linux線程的同步

互斥量、讀寫鎖、條件變量
線程安全
多線程編程環境中,多個線程同時調用某些函數可能會產生錯誤結果,這些函數稱爲非 線程安全函數。如果庫函數能夠在多個線程中同時執行並且不會互相干擾,那麼這個庫函數 就是線程安全(thread-safe)函數。

什麼是互斥量
互斥量(Mutex),又稱爲互斥鎖,是一種用來保護臨界區的特殊變量,它可以處於鎖 定(locked)狀態,也可以處於解鎖(unlocked)狀態:
1、如果互斥鎖是鎖定的,就是一個特定的線程持有這個互斥鎖;
2、如果沒有線程持有這個互斥鎖,那麼這個互斥鎖就處於解鎖狀態。

每個互斥鎖內部有一個線程等待隊列,用來保存等待該互斥鎖的線程。當互斥鎖處於解 鎖狀態時,一個線程試圖獲取這個互斥鎖時,這個線程就可以得到這個互斥鎖而不會阻塞; 當互斥鎖處於鎖定狀態時,一個線程試圖獲取這個互斥鎖時,這個線程將阻塞在互斥鎖的等 待線程隊列內。 互斥量是最簡單也是最有效的線程同步機制。程序可以用它來保護臨界區,以獲得對排 它性資源的訪問權。另外,互斥量只能被短時間地持有,使用完臨界資源後應立即釋放鎖。

爲什麼要使用互斥量
當多個線程共享相同的內存時,需要每一個線程看到相同的視圖,當一個線程修改變量時,而其他線程也可以讀取或者修改這個變量,就需要對這些線程同步,確保他們不會訪問到無效的變量

pthreads 使用 pthread_mutex_t 類型的變量來表示互斥量,同時在使用互斥量進行同步時 需要先對它進行初始化,可以靜態或動態方式對互斥量進行初始化。

(1)靜態初始化 對是靜態分配的 pthread_mutex_t 變量來說值需要將 PTHREAD_MUTEX_INITIALIZER 賦給變量就行
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

(2)動態初始化 對動態分配或者不使用默認互斥屬性的互斥變量來說,需要調用 pthread_mutex_int()函 數來執行初始化工作。pthread_mutex_int()函數原型如下:
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);

參數 mutex 是一個指向要初始化的互斥量的指針;參數 attr 傳遞 NULL 來初始化一個帶 有默認屬性的互斥量,否則就要用類似於線程屬性對象所使用的方法,先創建互斥量屬性對 象,再用該屬性對象來創建互斥量。

函數成功返回 0,否則返回一個非 0 的錯誤碼,表 13.5 列出 pthread_mutex_init 出錯的 錯誤碼。

在這裏插入圖片描述
加鎖
1.線程試圖鎖定互斥量的過程稱之爲加鎖。
Pthreads 中有兩個試圖鎖定互斥量的函數,pthread_mutex_lock()和 pthread_mutex_ trylock()。pthread_mutex_lock()函數會一直阻塞到互斥量可用爲止,而pthread_mutex_trylock() 會嘗試加鎖,通常立即返回。
函數原型如下:
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
參數 mutex 是需要加鎖的互斥量。函數成功返回 0,否則返回一個非 0 的錯誤碼,其中 另一個線程已持有鎖的情況下,調用 pthread_mutex_trylock()函數是錯誤碼爲 EBUSY。

解鎖

  1. 解鎖是線程將互斥量由鎖定狀態變爲解鎖狀態。 pthread_mutex_unlock()函數用來釋放指定的互斥量。函數原型如下:
    int pthread_mutex_unlock(pthread_mutex_t *mutex); 參數 mutex 是需要加鎖的互斥量。函數成功返回 0,否則返回一個非 0 的錯誤碼。

在這裏插入圖片描述
圖解:當線程A修改寄存器的值的時候,線程B就讀取寄存器的值,線程B本應該是讀取線程A改變後的值,但是讀取到了之前的值,所以線程之間沒有同步

用一個例子證明線程之間沒有同步,導致線程運用的值相同

#include"myhand.h"


struct studen
{
        int id;
        int age;
        int name;

}stu;
pthread_mutex_t mut;//互斥量的初始化,因爲都要用,所以用全局變量
int i;
void *thread_fun1(void *arg)
{
        printf("thread_fun1\n");
        //不斷的打印他們的值,判斷他們的值是否相等,相等的話就說明他們值不同步
        while(1)
        {
                stu.id = i;
                stu.age = i;
                stu.name = i;

}stu;
pthread_mutex_t mutex;//互斥量的初始化,因爲都要用,所以用全局變量
int i;
void *thread_fun1(void *arg)
{
        printf("thread_fun1\n");
        //不斷的打印他們的值,判斷他們的值是否相等,相等的話就說明他們值不同步
        while(1)
        {
                stu.id = i;
                stu.age = i;
                stu.name = i;

                if(stu.id != stu.age || stu.id != stu.name ||stu.age != stu.name)
                {
                        printf("%d,%d,%d\n",stu.id,stu.age,stu.name);
                        break;
                }
                i++;
        pthread_mutex_lock(&mutex);//對互斥量進行加鎖
void *thread_fun1(void *arg)
{
        printf("thread_fun1\n");
        //不斷的打印他們的值,判斷他們的值是否相等,相等的話就說明他們值不同步
        while(1)
        {
                pthread_mutex_lock(&mutex);//對互斥量進行加鎖
                stu.id = i;
                stu.age = i;
                stu.name = i;
                pthread_mutex_unlock(&mutex);//對互斥量進行解鎖,同步加鎖

                if(stu.id != stu.age || stu.id != stu.name ||stu.age != stu.name)
                {
                        printf("%d,%d,%d\n",stu.id,stu.age,stu.name);
                        break;
                }
                i++;
                pthread_mutex_unlock(&mutex);//對互斥量進行解鎖,同步加鎖
        }
        return (void *)0;
}
void *thread_fun2(void *arg)
{
        printf("thread fun2\n");
        //不斷的打印他們的值,判斷他們的值是否相等,相等的話就說明他們值不同步
        while(1)
        {
                pthread_mutex_lock(&mutex);//對互斥量進行解鎖,同步加鎖,防止產生錯亂
                stu.id = i;
                stu.age = i;
                stu.name = i;
                i++;
                if(stu.id != stu.age || stu.id != stu.name ||stu.age != stu.name)
                {
                        printf("%d,%d,%d\n",stu.id,stu.age,stu.name);
                        break;
                }
                pthread_mutex_unlock(&mutex);//對互斥量進行解鎖,同步加鎖,這樣其他線>程才能訪問
        
        }
        return (void *)0;
}
int main()
{
        pthread_t tid1, tid2;
        int err;
        err = pthread_mutex_init(&mutex,NULL);//對互斥量的初始化
        if( err != 0)//因爲成功的話是返回0
        {
                printf("init pthread_mutex failure\n");
                return -1;
        }
        err = pthread_create(&tid1,NULL,thread_fun1,NULL);
        if(err != 0)
        {
                printf("creat new thread failure\n");
                return -1;
        }
        err = pthread_create(&tid2,NULL,thread_fun2,NULL);
        if(err != 0)
        {
                printf("creat new thread failure\n");
                return -1;
        }

        pthread_join(tid1,NULL);//因爲線程是死循環,所以要分離出線程    
        pthread_join(tid2,NULL);//因爲線程是死循環,所以要分離出線程    

        return 0;
}

在這裏插入圖片描述
這裏沒有值說明兩個線程在執行的時候,i的值在不斷的進行++操作,但是線程之間沒有使用相同的變量進行操作,thread1先獲得互斥鎖,先運行,後再運行thread2

讀寫鎖

讀寫鎖與互斥量類似,不過讀寫鎖有更高的並行行,互斥量要麼加鎖要麼不加鎖,而且同一時刻只允許一個線程對其加鎖,對於一個變量的讀取,完全可以讓多線程同時進行操作。

pthread_rwlock_t rwlocl
讀寫鎖有三種狀態:讀模式下加鎖,寫模式下加鎖,不加鎖。
一次只有一個線程可以佔有寫模式下的讀寫鎖,但是多線程可以同時佔有讀模式的讀寫鎖

讀寫鎖再寫加鎖狀態時,在它被解鎖之前,所有試圖對這個鎖加鎖的線程都會阻塞,讀寫鎖在讀加鎖狀態時,所有試圖以讀模式對其加鎖的線程都會獲得訪問權,但是如果線程希望以寫模式對其加鎖,它必須阻塞直到所有線程釋放鎖

當讀寫鎖在讀模式下加鎖時,如果有線程試圖以寫模式對其加鎖,那麼讀寫鎖會阻塞隨後的讀模式鎖的請求。這樣就可以避免讀鎖長期佔用,而寫鎖達不到請求。

讀寫鎖非常適合對數據結構讀次數大於寫次數,當它以讀模式鎖住時,是以共享的方式鎖住,當他以寫模式鎖住時,是以獨佔的模式鎖住的

讀寫鎖使用前初始化
int pthread_rwlock_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr)
使用完需要銷燬
銷燬互斥量
銷燬互斥量使用 pthread_mutex_destroy()函數,原型如下:
int pthread_mutex_destroy(pthread_mutex_t *mutex);
成功返回0,失敗返回錯誤碼

讀模式加鎖

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
寫模式加鎖

int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

解鎖

int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

成功返回0

在這裏插入圖片描述

條件變量
在一條生成線上有一個倉庫,當生產者生成的時候會鎖住這個倉庫獨佔,而消費者取產品的時候也是鎖住這個倉庫獨佔,如果生產者發現倉庫滿了,那麼他就不能生產了,變成阻塞狀態,但是此時生產者獨佔倉庫,消費者無法進入倉庫去銷燬,就會變成殭屍狀態
當互斥量被鎖住以後發現當前線程還是無法完成之間的操作,那麼他應該被釋放互斥量,讓其他線程工作

1、可以採用輪詢方式,不停的詢問你需要的條件
2、讓系統幫你查詢條件,使用條件變量pthread_cond_t cond

條件變量初始化
(1)靜態初始化
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
(2)動態初始化
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);

參數
參數 cond 是一個指向需要初始化 pthread_cond_t 變量的指針,參數 attr 傳遞 NULL 值 時,pthread_cond_init()將 cond 初始化爲默認屬性的條件變量。

函數返回值
函數成功將返回 0;否則返回一個非 0 的錯誤碼。

銷燬條件變量
int pthread_cond_destroy(pthread_cond_t *cond);
函數成功調用返回 0,否則返回一個非 0 的錯誤碼

等待
條件變量是與條件測試一起使用的,通常線程會對一個條件進行測試,如果條件不滿足 就會調用條件等待函數來等待條件滿足。
條件等待函數有 pthread_cond_wait()pthread_cond_timedwait()和兩個,
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);

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

pthread_cond_wait()函數在條件不滿足時將一直等待,而 pthread_cond_timedwait()將只 等待一段時間

參數值:
參數 cond 是一個指向條件變量的指針,參數 mutex 是一個指向互斥量的指針,線程在 調用前應該擁有這個互斥量,當線程要加入條件變量的等待隊列時,等待操作會使線程釋放 這個互斥量。pthread_timedwait()的第三個參數 abstime 是一個指向返回時間的指針,如果條 件變量通知信號沒有在此等待時間之前出現,等待將超時退出,abstime 是個絕對時間,而不是時間間隔。

返回值:
以上函數成功調用返回 0,否則返回非 0 的錯誤碼,其中 pthread_cond_timedwait()函 數如果 abstime 指定的時間到期,錯誤碼爲 ETIMEOUT

通知:
當另一個線程修改了某參數可能使得條件變量所關聯的條件變成真時,它應該通知一個 或者多個等待在條件變量等待隊列中的線程。
條件通知函數有 pthread_cond_signal()和 pthread_cond_broadcast()函數
其中 pthread_ cond_signal 函數可以喚醒一個在條件變量等待隊列等待的線程,而 pthread_cond_broadcast 函數可以所有在條件變量等待隊列等待的線程。

函數原型如下:
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);

參數 cond 是一個指向條件變量的指針。

函數成功返回 0,否則返回一個非 0 的錯誤碼。

在這裏插入圖片描述

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