多線程編程基礎


線程的基本函數   

  1.線程創建:

#include <pthread.h>

int pthread_create(pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);

參數說明:

thread:指向pthread_create類型的指針,用於引用新創建的線程。

attr:用於設置線程的屬性,一般不需要特殊的屬性,所以可以簡單地設置爲NULL。

*(*start_routine)(void *):傳遞新線程所要執行的函數地址。

arg:新線程所要執行的函數的參數。

調用如果成功,則返回值是0,如果失敗則返回錯誤代碼。

可以調用pthread_self( )獲取當前線程的id

 

2.線程終止

    如果需要只終止某個線程而不終止整個進程,可以有三種方法:

  • 從線程函數return。這種方法對主線程不適用,從main函數return相當於調用exit。

  • 一個線程可以調用pthread_cancel終止同一進程中的另一個線程。

  • 線程可以調用pthread_exit終止自己。


#include <pthread.h>

void pthread_exit(void *retval);

參數說明:

retval:返回指針,指向線程向要返回的某個對象。

線程通過調用pthread_exit函數終止執行,並返回一個指向某對象的指針。注意:絕不能用它返回一個指向局部變量的指針,因爲線程調用該函數後,這個局部變量就不存在了,這將引起嚴重的程序漏洞。

 

3.線程等待

#include <pthread.h>

int pthread_join(pthread_t th, void **thread_return);

參數說明:

th:將要等待的張璐,線程通過pthread_create返回的標識符來指定。

thread_return:一個指針,指向另一個指針,而後者指向線程的返回值。


有關分離線程

在任何一個時間點上,線程是可結合的(joinable)或者是分離的(detached)。一個可結合的線程能夠被其他線程收回其資源和殺死。在被其他線程回收之前,它的存儲器資源(例如棧)是不釋放的。相反,一個分離的線程是不能被其他線程回收或殺死的,它的存儲器資源在它終止時由系統自動釋放。

默認情況下,線程被創建成可結合的。爲了避免存儲器泄漏,每個可結合線程都應該要麼被顯示地回收,即調用pthread_join;要麼通過調用pthread_detach函數被分離。如果一個可結合線程結束運行但沒有被join,則它的狀態類似於進程中的Zombie Process,即還有一部分資源沒有被回收,所以創建線程者應該調用pthread_join來等待線程運行結束,並可得到線程的退出代碼,回收其資源。由於調用pthread_join後,如果該線程沒有運行結束,調用者會被阻塞。

線程同步與互斥

A.mutex (互斥量)

原子操作:要麼都執行,要麼都不執行

對於多線程的程序,訪問衝突的問題是很普遍的,解決的辦法是引入互斥鎖(Mutex,MutualExclusiveLock),獲得鎖的線程可以完成“讀-修改-寫”的操作,然後釋放鎖給其它線程,沒有獲得鎖的線程只能等待而不能訪問共享數據,這樣“讀-修改-寫”三步操作組成一個原子操作,要麼都執行,要麼都不執行,不會執行到中間被打斷,也不會在其它處理器上並行做這個操作。Mutex用pthread_mutex_t類型的變量表示,可以這樣初始化和銷燬:

wKioL1e4J4DjTg8OAABUEE9GrXs387.jpg

返回值:成功返回0,失敗返回錯誤號。

pthread_mutex_init函數對Mutex做初始化,參數attr設定Mutex的屬性,如果attr爲NULL則表示缺省屬性,本章不詳細介紹Mutex屬性,感興趣的讀者可以參考[APUE2e]。用pthread_mutex_init函數初始化的Mutex可以用pthread_mutex_destroy銷燬。如果Mutex變量是靜態分配的(全局變量或static變量),也可以用宏定義PTHREAD_MUTEX_INITIALIZER操作可以用下列函數:

wKiom1e4J_TgrKuJAABRIIuDb6Y787.jpg

返回值:成功返回0,失敗返回錯誤號。

一個線程可以調用pthread_mutex_lock獲得Mutex,如果這時另一個線程已經調用pthread_mutex_lock獲得了該Mutex,則當前線程需要掛起等待,直到另一個線程調用pthread_mutex_unlock釋放Mutex,當前線程被喚醒,才能獲得該Mutex並繼續執行。如果一個線程既想獲得鎖,又不想掛起等待,可以調用pthread_mutex_trylock,如果Mutex已經被 另一個線程獲得,這個函數會失敗返回EBUSY,而不會使線程掛起等待。

一般情況下,如果同一個線程先後兩次調用lock,在第二次調用時,由於鎖已經被佔用,該線程會掛起等待別的線程釋放鎖,然而鎖正是被自己佔用着的,該線程又被掛起而沒有機會釋放鎖,

因此就永遠處於掛起等待狀態了,這叫做死鎖(Deadlock)。另一種典型的死鎖情形是這樣:線程A獲得了鎖1,線程B獲得了鎖2,這時線程A調用lock試圖獲得鎖2,結果是需要掛起等待線程B釋放鎖2,而這時線程B也調用lock試圖獲得鎖1,結果是需要掛起等待線程A釋放鎖1,於是線程A和B都永遠處於掛起狀態了。不難想象,如果涉及到更多的線程和更多的鎖,有沒有可能死鎖的問題將會變得複雜和難以判斷。

B. Condition Variable(條件變量)

wKiom1e4KaSyJJG6AABOaA1xKvE344.jpg

返回值:成功返回0,失敗返回錯誤號。

和Mutex的初始化和銷燬類似,pthread_cond_init函數初始化一個Condition Variable,attr參數爲NULL則表示缺省屬性,pthread_cond_destroy函數銷燬一個Condition Variable。如果ConditionVariable是靜態分配的,也可以用宏定義PTHEAD_COND_INITIALIZER初始化,相當於用pthread_cond_init函數初始化並且attr參數爲NULL。Condition Variable的操作可以用下列函數:

wKioL1e4KkrDfcQHAABK8q1XcDs817.jpg

wKiom1e4KkqQ58fxAAAzgXthsqo137.jpg

返回值:成功返回0,失敗返回錯誤號。

可見,一個Condition Variable總是和一個Mutex搭配使用的。一個線程可以調用pthread_cond_wait在一個Condition Variable上阻塞等待,這個函數做以下三步操作:

1. 釋放Mutex

2. 阻塞等待

3. 當被喚醒時,重新獲得Mutex並返回

c. Semaphore(信號量)

Mutex變量是非0即1的,可看作一種資源的可用數量,初始化時Mutex是1,表示有一個可用資源,加鎖時獲得該資源,將Mutex減到0,表示不再有可用資源,解鎖時釋放該資源,將Mutex重新加到1,表示又有了一個可用資源。semaphore變量的類型爲sem_t,sem_init()初始化一個semaphore變量,value參數表示可用資源的數量,pshared參數爲0表示信號量用於同一進程的線程間同步,這裏只介紹這種情況。在用完semaphore變量之後應該調用sem_destroy()釋放與semaphore相關的資源。

wKioL1e4LnuBejb1AABI8zwgH4g096.jpg

調用sem_wait()可以獲得資源(P操作),使semaphore的值減1,如果調用sem_wait()時semaphore的值已經是0,則掛起等待。如果不希望掛起等待,可以調用sem_trywait()。調用sem_post()可以釋放資源(V操作),使semaphore的值加1,同時喚醒掛起等待的線程。

示例:

生產者-消費者模型,基於固定大小的環形隊

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <signal.h>
#include <string.h>
#include <semaphore.h>

#define _SEM_PRO_ 64
#define _SEM_CON_ 0

sem_t sem_product;
sem_t sem_consume;

pthread_mutex_t con_lock;
pthread_mutex_t pro_lock;

int bank[_SEM_PRO_];

void* producter(void* _val)
{
    int index_pro = 0;
    while(1)
    {
        sem_wait(&sem_product);
        pthread_mutex_lock(&pro_lock);

        int _product = rand()%1000;
        bank[index_pro] = _product;
        printf("product1 done...val is : %d \n", _product);

        pthread_mutex_unlock(&pro_lock);
        sem_post(&sem_consume);

        ++index_pro;
        index_pro = index_pro%_SEM_PRO_;

        //sleep(1);
    }
}

void* producter2(void* _val)
{
    int index_pro = 0;
    while(1)
    {
        sem_wait(&sem_product);
        pthread_mutex_lock(&pro_lock);
        
        int _product = rand()%1000;
        bank[index_pro] = _product;
        printf("product2 done...val is : %d \n", _product);

        pthread_mutex_unlock(&pro_lock);
        sem_post(&sem_consume);

        ++index_pro;
        index_pro = index_pro%_SEM_PRO_;

        //sleep(1);
    }
}

void* consumer(void* _val)
{
    int index_con = 0;
    while(1)
    {

        sem_wait(&sem_consume);
        pthread_mutex_lock(&con_lock);

        int _consume = bank[index_con];
        printf("consume1 done...val is : %d\n", _consume);

        pthread_mutex_unlock(&con_lock);
        sem_post(&sem_product);
        
        ++index_con;
        index_con = index_con%_SEM_PRO_;

        //sleep(1);
    }
}

void* consumer2(void* _val)
{
    int index_con = 0;
    while(1)
    {
        sem_wait(&sem_consume);
        pthread_mutex_lock(&con_lock);

        int _consume = bank[index_con];
        printf("consume2 done...val is : %d\n", _consume);

        pthread_mutex_unlock(&con_lock);
        sem_post(&sem_product);
        
        ++index_con;
        index_con = index_con%_SEM_PRO_;

        //sleep(1);
    }
}

void run_product_consume()
{
    pthread_t tid_pro;
    pthread_t tid_con;
    pthread_t tid_con2;
    pthread_t tid_pro2;

    pthread_create(&tid_pro, NULL, producter, NULL);
    pthread_create(&tid_con, NULL, consumer, NULL);
    pthread_create(&tid_pro2, NULL, producter2, NULL);
    pthread_create(&tid_con2, NULL, consumer2, NULL);

    pthread_join(tid_pro, NULL);
    pthread_join(tid_con, NULL);
    pthread_join(tid_pro2, NULL);
    pthread_join(tid_con2, NULL);
}

void destroy_all_sem()
{
    printf("process done...\n");

    sem_destroy(&sem_product);
    sem_destroy(&sem_consume);

    pthread_mutex_destroy(&con_lock);
    pthread_mutex_destroy(&pro_lock);

    exit(0);
}

void init_all_sem()
{
    signal(2, destroy_all_sem);
    memset(bank, 0, sizeof(bank));

    sem_init(&sem_product, 0, _SEM_PRO_);
    sem_init(&sem_consume, 0, _SEM_CON_);

    pthread_mutex_init(&con_lock, NULL);
    pthread_mutex_init(&pro_lock, NULL);
}

int main()
{

    init_all_sem();
    run_product_consume();

    return 0;
}

d. 讀寫鎖


在編寫多線程的時候,有些公共數據修改的機會比較少。相比較改寫,它們讀的機會反而高的多。通常而言,在讀的過程中,往往伴隨着查找的操作,中間耗時很長。給這種代碼段加鎖,會極大地降低我們程序的效率。因此,讀寫鎖有派上了用場。

讀寫鎖實際是一種特殊的自旋鎖,它把對共享資源的訪問者劃分成讀者和寫者,讀者只對共享資源進行讀訪問,寫者則需要對共享資源進行寫操作。這種鎖相對於自旋鎖而言,能提高併發性,因爲在多處理器系統中,它允許同時有多個讀者來訪問共享資源,最大可能的讀者數爲實際的邏輯CPU數。寫者是排他性的,一個讀寫鎖同時只能有一個寫者或多個讀者(與CPU數相關),但不能同時既有讀者又有寫者。

wKioL1e4MS2R6YkgAAA52qnS9lU219.jpg

wKiom1e4MS7DW0wyAAAmJYT8fck967.jpg

wKioL1e4MS7iJgKxAABICiL23wk221.jpg

wKiom1e4MS7wLMXeAAA5kHn_CgU628.jpgj_0028.gifj_0028.gifj_0028.gifj_0028.gifj_0028.gifj_0028.gif






發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章