線程同步(POSIX)

線程同步

關於同步這個概念以及同步的處理方法,請看下面鏈接給出的文章:

https://blog.csdn.net/zy010101/article/details/83869140

經典的一些同步問題,請看下面的這一篇文章:

https://blog.csdn.net/zy010101/article/details/84439529

本文將講述POSIX標準下的線程同步相關的API。

互斥量

pthread提供了互斥量來確保同一時間只有一個線程訪問數據。互斥量在本質上講就是一把鎖,在訪問共享資源之前加鎖,之後解鎖。pthread下的互斥量是由pthread_mutex_t來定義。在使用互斥量之前,必須初始化它。可以選擇靜態初始化爲:

PTHREAD_MUTEX_INITALIZER,也可以通過pthread_mutex_init()函數來初始化。當然如果動態分配互斥量,那麼需要調用pthread_mutex_destory()來銷燬。

//函數原型
int pthread_mutex_init (pthread_mutex_t *__mutex,
			       const pthread_mutexattr_t *__mutexattr);

int pthread_mutex_destroy (pthread_mutex_t *__mutex);

需要使用默認屬性初始化互斥量時,__mutexattr設置爲NULL即可。

以上兩個函數成功返回0,否則返回錯誤編號。

對互斥量加鎖,需要調用pthread_mutex_lock()函數。如果互斥量已經上鎖了,那麼調用pthread_mutex_lock()的線程將會被阻塞直到互斥量被解鎖。對互斥量解鎖需要調用pthread_mutex_unlock()函數。如果不希望線程阻塞,那麼可以調用pthread_mutex_trylock()函數,它不會阻塞。當互斥量未加鎖,那麼pthread_mutex_trylock會給他加上鎖;否則,返回EBUSY。

下面的代碼是一個例子,在這個例子中,輸出設備是共享資源。主線程和子線程都需要在輸出設備上打印。首先,我們使用互斥量來給共享資源加上鎖,看一下執行結果。

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

pthread_mutex_t mutex1;

void *thread(void *arg)
{
   for (int i = 0; i < 10; i++)
   {
        pthread_mutex_lock(&mutex1);        
        printf("th");       //這裏打印th
        int i = 1000;
        while(0 != i)
        {
            i--;
        }
        printf("read\n");   //這裏打印read,合起來就是thread
        pthread_mutex_unlock(&mutex1);
        sleep(1);
   }
    pthread_exit(NULL);
}

int main()
{
    
    pthread_t tid;
    pthread_mutex_init(&mutex1,NULL);       //初始化鎖

    int t = pthread_create(&tid,NULL,thread,NULL);      //創建線程
    for (int i = 0; i < 10; i++)
    {
        pthread_mutex_lock(&mutex1);            //加鎖
        printf("ma");                           //這裏打印ma
        int i = 1000;
        while(0 != i)
        {
            i--;
        }
        printf("in\n");                         //這裏打印in。合起來就是打印main
        pthread_mutex_unlock(&mutex1);          //解鎖
        sleep(1);
    }
    pthread_join(tid,NULL);                     //等待子線程執行完畢,回收子線程。
    pthread_mutex_destroy(&mutex1);             //銷燬鎖
    return 0;
}

運行結果如下:

可以看到,主線程打印main以及子線程打印thread都是完整的。下面我們測試不加鎖的情形。代碼中註釋掉了鎖。

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

pthread_mutex_t mutex1;

void *thread(void *arg)
{
   for (int i = 0; i < 10; i++)
   {
        // pthread_mutex_lock(&mutex1);        
        printf("th");       //這裏打印th
        int i = 1000;
        while(0 != i)
        {
            i--;
        }
        printf("read\n");   //這裏打印read,合起來就是thread
        // pthread_mutex_unlock(&mutex1);
        sleep(1);
   }
    pthread_exit(NULL);
}

int main()
{
    
    pthread_t tid;
    // pthread_mutex_init(&mutex1,NULL);       //初始化鎖

    int t = pthread_create(&tid,NULL,thread,NULL);      //創建線程
    for (int i = 0; i < 10; i++)
    {
        // pthread_mutex_lock(&mutex1);            //加鎖
        printf("ma");                           //這裏打印ma
        int i = 1000;
        while(0 != i)
        {
            i--;
        }
        printf("in\n");                         //這裏打印in。合起來就是打印main
        // pthread_mutex_unlock(&mutex1);          //解鎖
        sleep(1);
    }
    pthread_join(tid,NULL);                     //等待子線程執行完畢,回收子線程。
    // pthread_mutex_destroy(&mutex1);             //銷燬鎖
    return 0;
}

運行結果如下:

很明顯可以看到,有時候是子線程正在打印,然後主線程搶奪去了設備進行打印,然後又被子線程搶奪,然後又被主線程搶奪回去。打印的結果是亂的。

使用互斥量的時候注意要初始化(而初始化對應這個destroy操作),所以這兩個操作是必備的。並且初始化互斥量是在創建子線程之前。

死鎖

關於死鎖的概念和死鎖的處理辦法的詳細描述,請看下面的文章:

https://blog.csdn.net/zy010101/article/details/94597934

既然操作系統不給我們提供死鎖避免和死鎖預防。那麼這個工作就很難進行。只能是我們程序員自己去儘量避免死鎖的產生。從理論上,我們知道了死鎖產生的情形。因此,我們程序員可以使用pthread_mutex_trylock()來避免死鎖的產生。使用這個pthread_mutex_trylock()只是去嘗試加鎖,加不上就算了,並且我可以把我已經拿到的鎖給釋放掉。等會兒我過來再問問。這雖然降低了併發的執行程度,但是它確實能有效的避免死鎖的產生。

Unix環境高級編程中有一段話說的很有價值。

多線程的軟件設計涉及鎖的折中。一方面鎖得粒度太粗(多個共享資源使用一把鎖或者是給非共享的資源也加鎖了),這會導致多線程阻塞等待相同的鎖,甚至是給有的線程已經使用完了共享資源,但是他給非共享的資源也加了鎖,導致共享資源明明已經空閒了,但是後面想要使用共享資源的線程依舊在等待。這導致的結果就是併發性的降低。另一方面鎖的粒度太細(每一個共享資源都來一把鎖,無論資源是否具有相關性),這會導致系統的開銷變大,並且使得代碼變得複雜。作爲一個程序猿,我們需要做的就是在滿足鎖的需求之下,在代碼複雜度和性能之間進行折中,找到一個平衡。

 

 

 

 

 

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