1. 程式人生 > >linux多執行緒程式設計之一多執行緒資料同步及相關api使用示例

linux多執行緒程式設計之一多執行緒資料同步及相關api使用示例

多執行緒的使用在編碼過程中非常常見,如果快速的理解和掌握linux下的多執行緒程式設計呢?下文即將為您揭曉一.linux多執行緒基本的建立及相關API使用:

1.執行緒的建立: int pthread_create(pthread_t thread, const pthread_attr_t attr, void (start_routine) (void ), void arg); 4個引數: 第一個引數:指向執行緒標示符pthread_t的指標; 第二個引數:設定執行緒的屬性 第三個引數:執行緒執行函式的起始地址 第四個引數:執行函式的引數

2.退出 pthread_exit 函式使執行緒退出並返回一個空指標型別的值,該值可以由下面即將介紹的函式pthread_join來獲取。

3.等待函式pthread_join用來等待一個執行緒的結束。 int pthread_join(pthread_t thread, void **retval); 引數 :thread: 執行緒識別符號,即執行緒ID,標識唯一執行緒。 retval: 使用者定義的指標,用來儲存被等待執行緒的返回值 返回值 : 0代表成功。 失敗,返回的則是錯誤號。

示例程式碼:

#include<pthread.h>
#include<stdlib.h>
#include<stdio.h>
#include<unistd.h>
void *  pthread_fun
(void * arg)
{ printf("the arg :%d \n", (int *)arg); pthread_exit((void*)6666); } intmain(){ int id = 999; void * p1_id; pthread_t thread; pthread_create(&thread, NULL, pthread_fun, (void *)id); pthread_join(thread, &p1_id); printf
("p1_id:%d\n", (int *)p1_id); printf("main thread id is %u\n",(unsigned)pthread_self()); return 0; }

//Compile and link with -pthread. result:the arg :999 p1_id:6666main thread id is 4155574048

二.linux 多執行緒的互斥鎖及相關API使用:

1.pthread_mutex_init int pthread_mutex_init(pthread_mutex_t restrict mutex, const pthread_mutexattr_t restrict attr); 互斥鎖的初始化。pthread_mutex_init()函式是以動態方式建立互斥鎖的,引數attr指定了新建互斥鎖的屬性。如果引數attr為NULL,則使用預設的互斥鎖屬性,預設屬性為快速互斥鎖。互斥鎖的屬性在建立鎖的時候指定.

2.pthread_mutex_destroy  int pthread_mutex_destroy(pthread_mutex_t *mutex); //mutex 指向要銷燬的互斥鎖的指標  互斥鎖銷燬函式在執行成功後返回 0,否則返回錯誤碼。

3.pthread_mutex_lock 當返回時,該互斥鎖已被鎖定。呼叫執行緒是該互斥鎖的屬主。如果該互斥鎖已被另一個執行緒鎖定和擁有,則呼叫執行緒將阻塞,直到該互斥鎖變為可用為止。

4.pthread_mutex_unlock 與pthread_mutex_lock成對存在。釋放互斥鎖

5.pthread_mutex_tylock(pthread_mutex_t *mutex);加鎖,但是與3不一樣的是當鎖已經在使用的時候,返回為EBUSY,而不是掛起等待。

下面來看看多執行緒的資料同步示例:

#include<stdlib.h>
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>

typedef struct ct_sum  
{ 
    int sum;  
    pthread_mutex_t m_tMutex;  
}ct_sum;

void * add1(void * cnt){       
    pthread_mutex_lock(&(((ct_sum*)cnt)->m_tMutex));  
    int i = 0;  
    for(i; i<50; i++)  
    {
            (*(ct_sum*)cnt).sum += i;  
    }  
    printf("add1 sum:%d\n", (*(ct_sum*)cnt).sum);
    pthread_mutex_unlock(&(((ct_sum*)cnt)->m_tMutex));  
    pthread_exit(NULL);  
    return 0;  
}  

void * add2(void *cnt){       
    int i = 50;   
    pthread_mutex_lock(&(((ct_sum*)cnt)->m_tMutex));  
    for(i; i<101; i++)  
    {
        (*(ct_sum*)cnt).sum += i;  
    }  
    printf("add2 sum:%d\n", (*(ct_sum*)cnt).sum);
    pthread_mutex_unlock(&(((ct_sum*)cnt)->m_tMutex));  
    pthread_exit(NULL);  
    return 0;  
}  


intmain(void){ 
    int i;  
    pthread_t ptid1, ptid2;  
    int sum = 0;  
    ct_sum cnt;  
    pthread_mutex_init(&(cnt.m_tMutex), NULL);  
    cnt.sum = 0;  

     pthread_create(&ptid1, NULL, add1, &cnt);  
    pthread_create(&ptid2, NULL, add2, &cnt);  

    pthread_join(ptid1, NULL);  
    pthread_join(ptid2, NULL);  

    printf("sum %d\n", cnt.sum);  
    pthread_mutex_destroy(&(cnt.m_tMutex));  
     return 0;  
}

三.linux多執行緒的互條件變數及相關API使用:

1.pthread_cond_init()被用來初始化一個條件變數。它的原型為:extern int pthread_cond_init P ((pthread_cond_t *cond,const pthread_condattr_t *cond_attr));其中cond是一個指向結構pthread_cond_t的指標,cond_attr是一個指向結構pthread_condattr_t的指標。結構pthread_condattr_t是條件變數的屬性結構,和互斥鎖一樣我們可以用它來設定條件變數是程序內可用還是程序間可用, 預設值是PTHREAD_PROCESS_PRIVATE,即此條件變數被同一程序內的各個執行緒使用; 如果選擇為PTHREAD_PROCESS_SHARED則為多個程序間各執行緒公用。 注意初始化條件變數只有未被使用時才能重新初始化或被釋放。返回值:函式成功返回0;任何其他返回值都表示錯誤。

也可以靜態的初始化條件變數  pthread_cond_t my_condition = PTHREAD_COND_INITIALIZER;

2.pthreadcond destroy(pthread_cond_t *cond)釋放一個條件變數的函式。

3.pthread_cond_signal函式的作用是傳送一個訊號給另外一個正在處於阻塞等待狀態的執行緒,使其脫離阻塞狀態,繼續執行.如果沒有執行緒處在阻塞等待狀態,pthread_cond_signal也會成功返回。

注意:使用pthread_cond_signal不會有“驚群現象”產生,他最多隻給一個執行緒發訊號。假如有多個執行緒正在阻塞等待著這個條件變數的話,那麼是根據各等待執行緒優先順序的高低確定哪個執行緒接收到訊號開始繼續執行。如果各執行緒優先順序相同,則根據等待時間的長短來確定哪個執行緒獲得訊號。但無論如何一個pthread_cond_signal呼叫最多發信一次。

4.pthread_cond_wait / pthread_cond_timedwait條件變數是利用執行緒間共享的全域性變數進行同步的一種機制,主要包括兩個動作: 一個執行緒等待"條件變數的條件成立"而掛起; 另一個執行緒使"條件成立"(給出條件成立訊號)。 為了防止競爭,條件變數的使用總是和一個互斥鎖結合在一起。

其中pthread_cond_timedwait為計時等待,計時等待方式如果在給定時刻前條件沒有滿足,則返回ETIMEOUT,結束等待。無論哪種等待方式,都必須和一個互斥鎖配合,以防止多個執行緒同時請求pthread_cond_wait()(或pthread_cond_timedwait(),下同)的競爭條件(Race Condition)。mutex互斥鎖必須是普通鎖(PTHREAD_MUTEX_TIMED_NP)或者適應鎖(PTHREAD_MUTEX_ADAPTIVE_NP),且在呼叫pthread_cond_wait()前必須由本執行緒加鎖(pthread_mutex_lock()),而在更新條件等待佇列以前,mutex保持鎖定狀態,並在執行緒掛起進入等待前解鎖。在條件滿足從而離開pthread_cond_wait()之前,mutex將被重新加鎖,以與進入pthread_cond_wait()前的加鎖動作對應。

5.pthread_cleanup_push()/pthread_cleanup_pop()的相關介紹(下面的示例中將用到):執行緒終止有兩種情況:正常終止和非正常終止。執行緒主動呼叫pthread_exit()或者從執行緒函式中return都將使執行緒正常退出,這是可預見的退出方式;非正常終止是執行緒在其他執行緒的干預下,或者由於自身執行出錯(比如訪問非法地址)而退出,這種退出方式是不可預見的。不論是可預見的執行緒終止還是異常終止,都會存在資源釋放的問題,在不考慮因執行出錯而退出的前提下,如何保證執行緒終止時能順利的釋放掉自己所佔用的資源,特別是鎖資源,就是一個必須考慮解決的問題。

最經常出現的情形是資源獨佔鎖的使用:執行緒為了訪問臨界資源而為其加上鎖,但在訪問過程中被外界取消,如果執行緒處於響應取消狀態,且採用非同步方式響應,或者在開啟獨佔鎖以前的執行路徑上存在取消點,則該臨界資源將永遠處於鎖定狀態得不到釋放。外界取消操作是不可預見的,因此的確需要一個機制來簡化用於資源釋放的程式設計。

線上程API中提供了一個pthread_cleanup_push()/pthread_cleanup_pop()函式對用於自動釋放資源 --從pthread_cleanup_push()的呼叫點到pthread_cleanup_pop()之間的程式段中的終止動作(包括呼叫 pthread_exit()和取消點終止)都將執行pthread_cleanup_push()所指定的清理函式。示例程式碼:

#include<pthread.h>
#include<unistd.h>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>

 //這裡我們採用靜態初始化
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

struct node 
{
    int n_number;
    struct node *n_next;
} *head=NULL; /*[thread_func]*/

/*釋放節點記憶體*/
staticvoidcleanup_handler(void*arg){
    printf("Clean up handler of second thread.\n");
    free(arg);
    (void)pthread_mutex_unlock(&mtx);
}

staticvoid *thread_func(void *arg){
    struct node*p = NULL;
    pthread_cleanup_push(cleanup_handler, p);

    pthread_mutex_lock(&mtx);
    //這個mutex_lock主要是用來保護wait等待臨界時期的情況,
    //當在wait為放入佇列時,這時,已經存在Head條件等待啟用
    //的條件,此時可能會漏掉這種處理
    //這個while要特別說明一下,單個pthread_cond_wait功能很完善,
    //為何這裡要有一個while(head==NULL)呢?因為pthread_cond_wait
    //裡的執行緒可能會被意外喚醒,如果這個時候head==NULL,
    //則不是我們想要的情況。這個時候,
    //應該讓執行緒繼續進入pthread_cond_wait
    while(1) 
    {
        while(head == NULL) 
        {
            pthread_cond_wait(&cond, &mtx);
        }
        //pthread_cond_wait會先解除之前的pthread_mutex_lock鎖定的mtx,
        //然後阻塞在等待佇列裡休眠,直到再次被喚醒
        //(大多數情況下是等待的條件成立而被喚醒,喚醒後,
        //該程序會先鎖定先pthread_mutex_lock(&mtx);,
        //再讀取資源用這個流程是比較清楚的
        /*block-->unlock-->wait()return-->lock*/
        p = head;
        head = head->n_next; 
        printf("Got %d from front of queue\n", p->n_number);
        //釋放主執行緒中申請的記憶體
        free(p); 
    }
    pthread_mutex_unlock(&mtx);//臨界區資料操作完畢,釋放互斥鎖
    pthread_cleanup_pop(0);
    return 0;
}

intmain(void){
    pthread_t tid;
    int i;
    struct node *p;
    pthread_create(&tid, NULL, thread_func, NULL);
    //子執行緒會一直等待資源,類似生產者和消費者,
    //但是這裡的消費者可以是多個消費者,
    //而不僅僅支援普通的單個消費者,這個模型雖然簡單,
    //但是很強大
    for (i=0; i<10; i++) 
    {
        p = (struct node*)malloc(sizeof(struct node));
        p->n_number = i;
        pthread_mutex_lock(&mtx);//需要操作head這個臨界資源,先加鎖,
        p->n_next = head;
        head = p;
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&mtx);//解鎖
        sleep(1);
    }
    printf("thread1 wanna end the cancel thread2.\n");
    pthread_cancel(tid);
    //關於pthread_cancel,有一點額外的說明,它是從外部終止子執行緒,
    //子執行緒會在最近的取消點,退出執行緒,而在我們的程式碼裡,最近的
    //取消點肯定就是pthread_cond_wait()了。
    pthread_join(tid, NULL);
    printf("Alldone--exiting\n");
    return 0;
}