線程-線程同步

我們知道線程共享同一進程內的資源。如果每個線程使用的變量,其他線程都不會讀取個修改,那麼就不存在一致性問題。相同的,如果變量只讀,多個線程同時讀取該變量也不會存在一致性問題。但是,當一個線程可以修改變量,其他線程可以讀取或修改變量,那麼就要對這些線程進行同步,確保多個線程訪問數據安全,不會訪問到無效數據。
兩個或多個線程同時修改同一變量時,也需要同步。跟前面信號講的問題相同,考慮增量操作情況。增量操作通常可以分爲三個步驟:
(1)從內存單元中讀入寄存器
(2)從寄存器變量進行增量操作
(3)將寄存器的值寫回內存
這就和信號量當時的可重入性是同樣的問題。
對於多線程程序,訪問衝突的問題是很普遍的,解決的辦法就是引入互斥鎖。

互斥量

互斥量本質來說是一把鎖,在訪問共享資源前對互斥量進行設置(加鎖),在訪問完成後釋放 (解鎖)互斥量。對互斥量進行加鎖後,任何其他試圖再次對互斥量加鎖的線程都會被阻塞,直到當前線程釋放該互斥鎖。
互斥變量用pthread_mutex_t數據類型表示。在使用互斥變量之前,比如首先對它進行初始化,可以把它設置爲常量PTHREAD_MUTEX_INITIALIZER(靜態分配),也可以調用pthread_mutex_init函數進行初始化。動態分配互斥量,釋放前需要調用pthread_mutex_destory。

 #include <pthread.h>

       int pthread_mutex_destroy(pthread_mutex_t *mutex);
       int pthread_mutex_init(pthread_mutex_t *restrict mutex,
              const pthread_mutexattr_t *restrict attr);
       pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
       兩函數返回值:若成功,返回0;否則返回錯誤編碼

attr設置爲NULL,用默認初始化互斥量。

加鎖解鎖函數:
對互斥量進行加鎖,調用pthread_mutex_lock。如果互斥量已經上鎖,調用線程將會阻塞,知道互斥量被解鎖。對互斥量解鎖,嗲用pthread_mutex_unlock。

include <pthread.h>
       int pthread_mutex_lock(pthread_mutex_t *mutex);//
       int pthread_mutex_trylock(pthread_mutex_t *mutex);
       int pthread_mutex_unlock(pthread_mutex_t *mutex);
     返回值:若成功,返回;否則,返回錯誤編碼

當然如果線程不希望被阻塞,可以調用trylock對互斥量加鎖。

/*************************************************************************
    > File Name: mutex.c
    > Author: 
    > Mail: 
    > Created Time: Thu 15 Jun 2017 11:35:04 PM PDT
 ************************************************************************/
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<unistd.h>
int count=0;
void *thread(void *tval)
{
    int i=0;  
    while(i<5001)
    {
        int tmp=count;
        i++;
        printf("pthread:%lu,count:%d\n",pthread_self(),count);
        count=tmp+1;
    }  
    return NULL;
}
int main()
{
    pthread_t tid1;
    pthread_t tid2;
    pthread_create(&tid1,NULL,&thread,NULL);
    pthread_create(&tid2,NULL,&thread,NULL);
    pthread_join(tid1,NULL);
    pthread_join(tid2,NULL);
    printf("count:%d",count);
    return 0;
}

這裏寫圖片描述
很明顯這不對,兩個線程tid1和tid2在跑while中的邏輯的時候,這個時候就會發生訪問衝突,count本來應該是10001的,但是因爲訪問衝突,所以最終的結果count結果要小於10000,原因上面我已經介紹過了,就不再重複,要想實現互斥,我們加上互斥鎖就好了。
使用互斥量之後:

/*************************************************************************
    > File Name: mutex.c
    > Author: 
    > Mail: 
    > Created Time: Thu 15 Jun 2017 11:35:04 PM PDT
 ************************************************************************/
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<unistd.h>
int count=0;
static pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
void *thread(void *tval)
{
    int i=0;  
    while(i<5001)
    {
        pthread_mutex_lock(&mutex);
        int tmp=count;
        i++;
        printf("pthread:%lu,count:%d\n",pthread_self(),count);
        count=tmp+1;
        pthread_mutex_unlock(&mutex);
    }  
    return NULL;
}
int main()
{
    pthread_t tid1;
    pthread_t tid2;
    pthread_create(&tid1,NULL,&thread,NULL);
    pthread_create(&tid2,NULL,&thread,NULL)    pthread_join(tid2,NULL);
    pthread_join(tid1,NULL);
     pthread_join(tid2,NULL);
    printf("coun`
:%d",count);
    return 0;
}

這裏寫圖片描述
加入互斥鎖之後的值纔是對的。

死鎖

什麼是死鎖?
所謂死鎖:是指兩個或兩個以上的進程在執行過程中,因爭奪資源而造成的一種互相等待的現象,若無外力作用,它們都將無法推進下去。此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的進程稱爲死鎖進程。
什麼時候可能產生死鎖?
(1)當一個線程對互斥量加鎖兩次。
(2)兩個線程都互相加鎖,並且不釋放。因爲兩個都在互相請求另一個線程資源,所以兩線程都無法向下運行,也是產生死鎖。
產生死鎖的原因主要是:
(1) 因爲系統資源不足。
(2) 進程運行推進的順序不合適。
(3) 資源分配不當等。
如果系統資源充足,進程的資源請求都能夠得到滿足,死鎖出現的可能性就很低,否則
就會因爭奪有限的資源而陷入死鎖。其次,進程運行推進順序與速度不同,也可能產生死鎖。
產生死鎖的四個必要條件:
(1) 互斥條件:一個資源每次只能被一個進程使用。
(2) 請求與保持條件:一個進程因請求資源而阻塞時,對已獲得的資源保持不放。
(3) 不剝奪條件:進程已獲得的資源,在末使用完之前,不能強行剝奪。
(4) 循環等待條件:若干進程之間形成一種頭尾相接的循環等待資源關係。
怎麼避免死鎖呢?
(1)加鎖順序(線程按照一定的順序加鎖)
(2)加鎖時限(線程嘗試獲取鎖的時候加上一定的時限,超過時限則放棄對該鎖的請求,並釋放自己佔有的鎖)
(3)死鎖檢測(每當一個線程獲得了鎖,會在線程和鎖相關的數據結構中(map、graph)等等將其記下。除此之外,每當有線程請求鎖,也需要記錄在這個數據結構中。
當一個線程請求鎖失敗時,這個線程可以遍歷鎖的關係圖看看是否有死鎖發生)

條件變量

條件變量是線程可用的另一種同步機制。條件變量給多個線程提供了一個會合的場所。條件變量與互斥量一起使用時,允許線程以無競爭的方式等待特定的條件發生。
條件本身是由互斥量保護的。線程在改變條件狀態之前必須首先鎖住互斥量。其他線程獲得互斥量之前不會察覺到這種改變,因爲互斥量必須在鎖定以後才能計算條件。
條件變量相關函數和互斥量相關函數大同小異。

  • 初始化和釋放條件變量
 #include <pthread.h>

       int pthread_cond_destroy(pthread_cond_t *cond);
       int pthread_cond_init(pthread_cond_t *restrict cond,
              const pthread_condattr_t *restrict attr);
       pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
       返回值:若成功,返回0;否則,返回錯誤編號

和互斥量一樣,可以直接用宏來初始化。attr可以爲NULL。

  • 等待條件變量爲真
#include <pthread.h>

       int pthread_cond_timedwait(pthread_cond_t *restrict cond,
              pthread_mutex_t *restrict mutex,
              const struct timespec *restrict abstime);
       int pthread_cond_wait(pthread_cond_t *restrict cond,
              pthread_mutex_t *restrict mutex);

     返回值:若成功,返回0;否則,返回錯誤編碼

傳遞給pthread_cond_wait的互斥量對條件進行保護。調用者把鎖住的互斥量傳遞給函數,函數然後自動把調用線程放到等待條件的線程列表上,並對互斥量解鎖。
pthread_cond_timedwait函數功能與pthread_cond_wait函數相似,只是多了一個超時。

  • 通知線程條件滿足
 #include <pthread.h>

       int pthread_cond_broadcast(pthread_cond_t *cond);
       int pthread_cond_signal(pthread_cond_t *cond);
       返回值:若成功,返回0;否則,返回錯誤編碼

下篇通過一個基於消費者生產者的模型來做例子。

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