1. 程式人生 > >UNP多執行緒程式設計技巧簡單總結

UNP多執行緒程式設計技巧簡單總結

1. 為新執行緒傳遞引數

錯誤程式碼示例:

    for (i = 0; i < N; i++)
    {
        pthread_create(&tid, NULL, &handle, &i);
    }

噹噹噹,要提問啦!!! 以上這段程式碼會發生什麼奇怪的事情嗎?當執行緒去進行處理i的時候,如果cpu排程到主執行緒執行,就會改變i的值。因為傳入的是地址,所以執行緒中使用的i就會被改變。這就會出現問題。那麼我們如何給執行緒傳遞引數吶?一般有以下兩種方法:

1.  傳送值而不傳送地址

2.  通過`new,malloc`傳遞
1.  傳送值而不傳送地址

for (i = 0; i < N; i++)
{
    pthread_create(&tid, NULL, &handle, (void *)i);
}


void *handle(void *arg)
{
    int i = (int)arg;
    。。。
    return 0;
}
 
2.  通過`new,malloc`傳遞

int *iptr = NULL;
for (i = 0; i < MAX; i++)
{
    iptr = (int *)malloc(sizeof(int));
    *iptr =
i; pthread_create(&tid[i], NULL, handle, iptr); } void *handle(void *arg) { int i = *((int *)arg); free(arg); 。。。 return 0; }

2. 執行緒特定資料

   在這裡我們分析並解決一個常見多執行緒程式設計錯誤:在函式中使用到靜態變數 。為什麼會錯?原因就是這些函式中使用到的靜態變數無法為不同的執行緒儲存各自的值。如果一個執行緒要操作該值,只會操作的是最終的數值。解決方法有三種:

  1. 使用執行緒特定資料。使得這些變數在每個執行緒中都獨自存在一份。執行緒私有的全域性變數,僅在某個執行緒中有效,但卻可以跨多個函式訪問。
  2. 改變呼叫順序。略
  3. 改變介面結構。避免使用靜態變數,如果是改寫我們前面的Recvline函式的話,就會降低效率!!!

比較常用的就是第一種方法。

相關函式如下:


int pthread_key_create(pthread_key_t *key, void (*destr_function) (void *));  
int pthread_key_delete(pthread_key_t key);  
  
int pthread_setspecific(pthread_key_t key, const void *pointer);  
void * pthread_getspecific(pthread_key_t key);  
  
pthread_once_t once_control = PTHREAD_ONCE_INIT;  
int pthread_once(pthread_once_t *once_control, void (*init_routine) (void));  

眾所周知,程序中有多個執行緒,執行緒中又有執行緒特定的資料元素(POSIX限定元素數量 >= 128 個),系統為每一個程序維護一個稱之為Key結構的陣列。如下所示:

   在這裡插入圖片描述

標誌:指示該陣列元素是否在使用。初始化自然是`不在使用中`
解構函式指標:一個執行緒終止時釋放執行緒特定資料的手段

另外,系統還會在程序內維護關於每個執行緒的一些資訊。這些資訊都在結構Pthread結構中儲存。pkeys是128個指標陣列,它與程序內的128個key息息相關。(其實我覺得可以認為是一樣子的),結構如下:

在這裡插入圖片描述

重點理解:

   當呼叫pthread_key_create 後會產生一個所有執行緒都可見的執行緒特定資料的鍵值(第一個在key陣列中不在使用的元素的索引,也會對應到pthread結構中。比如:發現key[1]空閒,就會返回1,其中pkey[1]也會被用的到。), **但是這個pkey[1]所指向的真實資料卻是不同的!**雖然都是pkey[1], 但是他們並不是指向同一塊記憶體,而是指向了只屬於自己的實際資料, 因此, 如果執行緒0更改了pkey[1]所指向的資料, 而並不能夠影響到執行緒 n ;

在這裡插入圖片描述

下面我來通過一個例項說明:

#include "../myhead.h"

static pthread_key_t global_key;
static pthread_once_t control_once = PTHREAD_ONCE_INIT; // 0
// 用來銷燬每個執行緒所指向的實際資料
static void pth_destructor(void *ptr)
{
    printf("銷燬執行緒。。。。。\n");
    free(ptr);
}
static void pro_once(void)
{
    printf("\t獲取程序鍵\n");
    pthread_key_create(&global_key, pth_destructor); //第二個引數是執行緒釋放函式
}
typedef struct
{
    int id;
    char *str;
} TT;
void *handle(void *arg)
{
    TT *tsd;
    char *tmp = ((TT *)arg)->str;
    int i = ((TT *)arg)->id;

    free(arg);

    pthread_once(&control_once, pro_once); //在程序範圍內只被呼叫一次,其實就是取得 key[n] 和 pkey[n]的索引

    if ((tsd = pthread_getspecific(global_key)) == NULL) //測試在當前這個執行緒中有沒有分配執行緒的實際資料
    {
        tsd = (TT *)malloc(sizeof(TT));
        tsd->id = i;
        tsd->str = tmp;
        pthread_setspecific(global_key, tsd); //存放執行緒特定資料
        printf("%d pthread setspecific, address: %p\n", i, tsd);
    }
    /*獲取*/
    TT *temp = pthread_getspecific(global_key);
    printf("temp->id ==  %d ,temp->str == %s \n", temp->id, temp->str);
    sleep(5);

    /*再次獲取*/
    temp = pthread_getspecific(global_key);
    printf("temp->id ==  %d ,temp->str == %s \n", temp->id, temp->str);
    
    pthread_exit(NULL);
}
int main()
{
    pthread_t tid1, tid2;
    int i;
    TT *iptr;
    for (i = 1; i < 3; i++)
    {
        iptr = (TT *)malloc(sizeof(TT));
        (*iptr).id = i;
        i == 1 ? ((*iptr).str = "11111111111") : ((*iptr).str = "222222222222222222222");
        pthread_create(&tid1, NULL, handle, iptr);
    }

    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);

    pthread_key_delete(global_key);

    return 0;
}

執行結果:
在這裡插入圖片描述

可以看出,我們的缺可以通過同一個key找到各個執行緒中的獨有的實際資料。更加具體的資訊就不說了。

3. 實現主執行緒等待任一執行緒結束

都知道,pthread_join()函式只能等待某一個執行緒結束,而不能等待任一執行緒結束。那麼當我們真的想要用到這一點的實現的時候有應該要用什麼來實現吶?答案就是條件變數。遠離就是:某個執行緒退出時,通過條件變數通知主執行緒。實現如下:


#include "../myhead.h"
#define MAX 10
/*利用條件變數實現 thr_join 等待任一執行緒結束 */
int ndone = 0;
pthread_mutex_t ndone_mutex;
pthread_cond_t ndone_cond;
int data[MAX];
int Decide = MAX;

void *handle(void *arg)
{
    sleep(5);

    pthread_mutex_lock(&ndone_mutex);
    printf("我要推出了哦!!我是 %d 號執行緒哦 !!!\n", pthread_self());
    ndone++;
    *((int *)arg) = 1;
    Decide--;
    pthread_cond_signal(&ndone_cond);
    pthread_mutex_unlock(&ndone_mutex);
    return 0;
}
int main(void)
{
    pthread_t tid[MAX];
    int i;
    Decide = MAX;
    for (i = 0; i < MAX; i++)
    {
        data[i] = 0;
        pthread_create(&tid[i], NULL, handle, &data[i]);
    }
    while (Decide > 0)
    {
        pthread_mutex_lock(&ndone_mutex);
        while (ndone == 0) //說明沒有任何執行緒退出
        {
            pthread_cond_wait(&ndone_cond, &ndone_mutex);
        }
        //一定有某個執行緒退出 
        for (i = 0; i < MAX; i++)
        {
            if (data[i] == 1){
                printf("pthread_join 等待 %d 執行緒退出 \n",tid[i]);
                pthread_join(tid[i], NULL);
                data[i] = 0 ;
            }
        }
        pthread_mutex_unlock(&ndone_mutex);
    }
    return 0;
}

執行結果:
在這裡插入圖片描述

至於為什麼每個條件變數都要關聯一個互斥鎖?是因為條件通常是執行緒之間共享的某個變數的值,允許不同執行緒設定和測試該變數就要求有一個與該變數關聯的互斥鎖。