互斥鎖
同步與互斥概述**
現代操作系統基本都是多任務操作系統,即同時有大量可調度實體在運行。在多任務操作系統中,同時運行的多個任務可能:
-
都需要訪問/使用同一種資源
-
多個任務之間有依賴關系,某個任務的運行依賴于另一個任務
這兩種情形是多任務編程中遇到的最基本的問題,也是多任務編程中的核心問題,同步和互斥就是用于解決這兩個問題的。
互斥:是指散步在不同任務之間的若干程序片斷,當某個任務運行其中一個程序片段時,其它任務就不能運行它們之中的任一程序片段,只能等到該任務運行完這個程序片段后才可以運行。最基本的場景就是:一個公共資源同一時刻只能被一個進程或線程使用,多個進程或線程不能同時使用公共資源。
同步:是指散步在不同任務之間的若干程序片斷,它們的運行必須嚴格按照規定的某種先后次序來運行,這種先后次序依賴于要完成的特定的任務。最基本的場景就是:兩個或兩個以上的進程或線程在運行過程中協同步調,按預定的先后次序運行。比如 A 任務的運行依賴于 B 任務產生的數據。
顯然,同步是一種更為復雜的互斥,而互斥是一種特殊的同步。也就是說互斥是兩個任務之間不可以同時運行,他們會相互排斥,必須等待一個線程運行完畢,另一個才能運行,而同步也是不能同時運行,但他是必須要按照某種次序來運行相應的線程(也是一種互斥)!因此互斥具有唯一性和排它性,但互斥并不限制任務的運行順序,即任務是無序的,而同步的任務之間則有順序關系。
為什么需要互斥鎖
在多任務操作系統中,同時運行的多個任務可能都需要使用同一種資源。這個過程有點類似于,公司部門里,我在使用著打印機打印東西的同時(還沒有打印完),別人剛好也在此刻使用打印機打印東西,如果不做任何處理的話,打印出來的東西肯定是錯亂的。
下面我們用程序模擬一下這個過程,線程一需要打印“ hello ”,線程二需要打印“ world ”,不加任何處理的話,打印出來的內容會錯亂:
測試程序:
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h> /* * 模擬一下這個過程,線程一需要打印“ hello ”,線程二需要打印“ world ”,* 不加任何處理的話,打印出來的內容會錯亂:*/void printer(char *str)
{while (*str != '\0'){putchar(*str);fflush(stdout);str++;sleep(1);}printf("\n");
}//線程一
void *thread_fun_1(void *arg)
{char *str = "hello";printer(str); //打印
}//線程二
void *thread_fun_2(void *arg)
{char *str = "world";printer(str); //打印
}int main()
{pthread_t tid1, tid2;//創建 2 個線程pthread_create(&tid1, NULL, thread_fun_1, NULL);pthread_create(&tid2, NULL, thread_fun_2, NULL);//等待線程結束,回收其資源pthread_join(tid1, NULL);pthread_join(tid2, NULL);return 0;
}
運行結果如下:
實際上,打印機是有做處理的,我在打印著的時候別人是不允許打印的,只有等我打印結束后別人才允許打印。這個過程有點類似于,把打印機放在一個房間里,給這個房間安把鎖,這個鎖默認是打開的。當 A 需要打印時,他先過來檢查這把鎖有沒有鎖著,沒有的話就進去,同時上鎖在房間里打印。而在這時,剛好 B 也需要打印,B 同樣先檢查鎖,發現鎖是鎖住的,他就在門外等著。而當 A 打印結束后,他會開鎖出來,這時候 B 才進去上鎖打印。
互斥鎖Mutex介紹
而在線程里也有這么一把鎖:互斥鎖(mutex),也叫互斥量,互斥鎖是一種簡單的加鎖的方法來控制對共享資源的訪問,互斥鎖只有兩種狀態,即加鎖( lock )和解鎖( unlock )。
互斥鎖的操作流程如下:
1)在訪問共享資源后臨界區域前,對互斥鎖進行加鎖。
2)在訪問完成后釋放互斥鎖導上的鎖。
3)對互斥鎖進行加鎖后,任何其他試圖再次對互斥鎖加鎖的線程將會被阻塞,直到鎖被釋放。
互斥鎖的數據類型是: pthread_mutex_t。
安裝對應幫助手冊:
sudo apt-get install manpages-posix-dev
pthread_mutex_init 函數
初始化互斥鎖:
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
功能:
????????初始化一個互斥鎖。
參數:
????????mutex:互斥鎖地址。類型是 pthread_mutex_t 。
????????attr:設置互斥量的屬性,通常可采用默認屬性,即可將 attr 設為 NULL。
可以使用宏 PTHREAD_MUTEX_INITIALIZER 靜態初始化互斥鎖,比如:
pthread_mutex_t ?mutex = PTHREAD_MUTEX_INITIALIZER;
這種方法等價于使用 NULL 指定的 attr 參數調用 pthread_mutex_init() 來完成動態初始化,不同之處在于 PTHREAD_MUTEX_INITIALIZER 宏不進行錯誤檢查。
返回值:
成功:0,成功申請的鎖默認是打開的。
失敗:非 0 錯誤碼
restrict,C語言中的一種類型限定符(Type Qualifiers),用于告訴編譯器,對象已經被指針所引用,不能通過除該指針外所有其他直接或間接的方式修改該對象的內容。
pthread_mutex_destroy函數
#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex);
功能:
銷毀指定的一個互斥鎖。互斥鎖在使用完畢后,必須要對互斥鎖進行銷毀,以釋放資源。
參數:
mutex:互斥鎖地址。
返回值:
成功:0
失敗:非 0 錯誤碼
pthread_mutex_lock函數
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
功能:
對互斥鎖上鎖,若互斥鎖已經上鎖,則調用者阻塞,直到互斥鎖解鎖后再上鎖。
參數:
mutex:互斥鎖地址。
返回值:
成功:0
失敗:非 0 錯誤碼
int pthread_mutex_trylock(pthread_mutex_t *mutex);
- 調用該函數時,若互斥鎖未加鎖,則上鎖,返回 0;
- 若互斥鎖已加鎖,則函數直接返回失敗,即 EBUSY。
pthread_mutex_unlock函數
#include <pthread.h>
int pthread_mutex_unlock(pthread_mutex_t *mutex);
功能:
????????對指定的互斥鎖解鎖。
參數:
????????mutex:互斥鎖地址。
返回值:
????????成功:0
????????失敗:非0錯誤碼
測試程序
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h> /* * 模擬一下這個過程,線程一需要打印“ hello ”,線程二需要打印“ world ”,* 加鎖的代碼,此時打印的結果不會錯亂*/pthread_mutex_t mutex; //互斥鎖//打印機
void printer(char *str)
{pthread_mutex_lock(&mutex); //上鎖while (*str != '\0'){putchar(*str);fflush(stdout);str++;sleep(1);}printf("\n");pthread_mutex_unlock(&mutex); //解鎖
}//線程一
void *thread_fun_1(void *arg)
{char *str = "hello";printer(str); //打印
}//線程二
void *thread_fun_2(void *arg)
{char *str = "world";printer(str); //打印
}int main(void)
{pthread_t tid1, tid2;pthread_mutex_init(&mutex, NULL); //初始化互斥鎖//創建 2 個線程pthread_create(&tid1, NULL, thread_fun_1, NULL);pthread_create(&tid2, NULL, thread_fun_2, NULL);//等待線程結束,回收其資源pthread_join(tid1, NULL);pthread_join(tid2, NULL);pthread_mutex_destroy(&mutex); //銷毀互斥鎖return 0;
}
結果:
死鎖(DeadLock)
1)什么是死鎖
死鎖是指兩個或兩個以上的進程在執行過程中,由于競爭資源或者由于彼此通信而造成的一種阻塞的現象,若無外力作用,它們都將無法推進下去。此時稱系統處于死鎖狀態或系統產生了死鎖,這些永遠在互相等待的進程稱為死鎖進程。
2)死鎖引起的原因
競爭不可搶占資源引起死鎖
也就是我們說的第一種情況,而這都在等待對方占有的不可搶占的資源。
競爭可消耗資源引起死鎖
有p1,p2,p3三個進程,p1向p2發送消息并接受p3發送的消息,p2向p3發送消息并接受p1的消息,p3向p1發送消息并接受p2的消息,如果設置是先接到消息后發送消息,則所有的消息都不能發送,這就造成死鎖。
進程推進順序不當引起死鎖
有進程p1,p2,都需要資源A,B,本來可以p1運行A --> p1運行B --> p2運行A --> p2運行B,但是順序換了,p1運行A時p2運行B,容易發生第一種死鎖。互相搶占資源。
3)死鎖的必要條件
互斥條件
- 某資源只能被一個進程使用,其他進程請求該資源時,只能等待,直到資源使用完畢后釋放資源。
請求和保持條件
- 程序已經保持了至少一個資源,但是又提出了新要求,而這個資源被其他進程占用,自己占用資源卻保持不放。
不可搶占條件
- 進程已獲得的資源沒有使用完,不能被搶占。
循環等待條件
- 必然存在一個循環鏈。
4)處理死鎖的思路
預防死鎖
????????破壞死鎖的四個必要條件中的一個或多個來預防死鎖。
避免死鎖
????????和預防死鎖的區別就是,在資源動態分配過程中,用某種方式防止系統進入不安全的狀態。
檢測死鎖
????????運行時出現死鎖,能及時發現死鎖,把程序解脫出來
解除死鎖
????????發生死鎖后,解脫進程,通常撤銷進程,回收資源,再分配給正處于阻塞狀態的進程。
5)預防死鎖的方法
破壞請求和保持條件
協議1:
所有進程開始前,必須一次性地申請所需的所有資源,這樣運行期間就不會再提出資源要求,破壞了請求條件,即使有一種資源不能滿足需求,也不會給它分配正在空閑的資源,這樣它就沒有資源,就破壞了保持條件,從而預防死鎖的發生。
協議2:
允許一個進程只獲得初期的資源就開始運行,然后再把運行完的資源釋放出來。然后再請求新的資源。
破壞不可搶占條件
當一個已經保持了某種不可搶占資源的進程,提出新資源請求不能被滿足時,它必須釋放已經保持的所有資源,以后需要時再重新申請。
破壞循環等待條件
對系統中的所有資源類型進行線性排序,然后規定每個進程必須按序列號遞增的順序請求資源。假如進程請求到了一些序列號較高的資源,然后有請求一個序列較低的資源時,必須先釋放相同和更高序號的資源后才能申請低序號的資源。多個同類資源必須一起請求。
讀寫鎖
讀寫鎖概述
當有一個線程已經持有互斥鎖時,互斥鎖將所有試圖進入臨界區的線程都阻塞住。但是考慮一種情形,當前持有互斥鎖的線程只是要讀訪問共享資源,而同時有其它幾個線程也想讀取這個共享資源,但是由于互斥鎖的排它性,所有其它線程都無法獲取鎖,也就無法讀訪問共享資源了,但是實際上多個線程同時讀訪問共享資源并不會導致問題。
在對數據的讀寫操作中,更多的是讀操作,寫操作較少,例如對數據庫數據的讀寫應用。為了滿足當前能夠允許多個讀出,但只允許一個寫入的需求,線程提供了讀寫鎖來實現。
讀寫鎖的特點如下:
1)如果有其它線程讀數據,則允許其它線程執行讀操作,但不允許寫操作。
2)如果有其它線程寫數據,則其它線程都不允許讀、寫操作。
讀寫鎖分為讀鎖和寫鎖,規則如下:
1)如果某線程申請了讀鎖,其它線程可以再申請讀鎖,但不能申請寫鎖。
2)如果某線程申請了寫鎖,其它線程不能申請讀鎖,也不能申請寫鎖。
POSIX 定義的讀寫鎖的數據類型是: pthread_rwlock_t。
pthread_rwlock_init函數
#include <pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
const pthread_rwlockattr_t *restrict attr);
功能:
用來初始化 rwlock 所指向的讀寫鎖。
參數:
rwlock:指向要初始化的讀寫鎖指針。
attr:讀寫鎖的屬性指針。如果 attr 為 NULL 則會使用默認的屬性初始化讀寫鎖,否則使用指定的 attr 初始化讀寫鎖。
可以使用宏 PTHREAD_RWLOCK_INITIALIZER 靜態初始化讀寫鎖,比如:
pthread_rwlock_t my_rwlock = PTHREAD_RWLOCK_INITIALIZER;
這種方法等價于使用 NULL 指定的 attr 參數調用 pthread_rwlock_init() 來完成動態初始化,不同之處在于PTHREAD_RWLOCK_INITIALIZER 宏不進行錯誤檢查。
返回值:
成功:0,讀寫鎖的狀態將成為已初始化和已解鎖。
失敗:非 0 錯誤碼。
pthread_rwlock_destroy函數
#include <pthread.h>
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
功能:
用于銷毀一個讀寫鎖,并釋放所有相關聯的資源(所謂的所有指的是由 pthread_rwlock_init() 自動申請的資源) 。
參數:
rwlock:讀寫鎖指針。
返回值:
成功:0
失敗:非 0 錯誤碼
pthread_rwlock_rdlock函數
#include <pthread.h>
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
功能:
以阻塞方式在讀寫鎖上獲取讀鎖(讀鎖定)。
如果沒有寫者持有該鎖,并且沒有寫者阻塞在該鎖上,則調用線程會獲取讀鎖。
如果調用線程未獲取讀鎖,則它將阻塞直到它獲取了該鎖。一個線程可以在一個讀寫鎖上多次執行讀鎖定。
線程可以成功調用 pthread_rwlock_rdlock() 函數 n 次,但是之后該線程必須調用 pthread_rwlock_unlock() 函數 n 次才能解除鎖定。
參數:
rwlock:讀寫鎖指針。
返回值:
成功:0
失敗:非 0 錯誤碼
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
- 用于嘗試以非阻塞的方式來在讀寫鎖上獲取讀鎖。
- 如果有任何的寫者持有該鎖或有寫者阻塞在該讀寫鎖上,則立即失敗返回。
pthread_rwlock_wrlock函數
#include <pthread.h>
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
功能:
在讀寫鎖上獲取寫鎖(寫鎖定)。
如果沒有寫者持有該鎖,并且沒有寫者讀者持有該鎖,則調用線程會獲取寫鎖。
如果調用線程未獲取寫鎖,則它將阻塞直到它獲取了該鎖。
參數:
rwlock:讀寫鎖指針。
返回值:
成功:0
失敗:非 0 錯誤碼
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
- 用于嘗試以非阻塞的方式來在讀寫鎖上獲取寫鎖。
- 如果有任何的讀者或寫者持有該鎖,則立即失敗返回。
pthread_rwlock_unlock函數
#include <pthread.h>
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
功能:
無論是讀鎖或寫鎖,都可以通過此函數解鎖。
參數:
rwlock:讀寫鎖指針。
返回值:
成功:0
失敗:非 0 錯誤碼
測試程序示例
下面是一個使用讀寫鎖來實現 4 個線程讀寫一段數據是實例。
在此示例程序中,共創建了 4 個線程,其中兩個線程用來寫入數據,兩個線程用來讀取數據。當某個線程讀操作時,其他線程允許讀操作,卻不允許寫操作;當某個線程寫操作時,其它線程都不允許讀或寫操作。
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h> /* * 創建了 4 個線程,其中兩個線程用來寫入數據,兩個線程用來讀取數據。* 當某個線程讀操作時,其他線程允許讀操作,卻不允許寫操作;當某個線程寫操作時,* 其它線程都不允許讀或寫操作。*/pthread_rwlock_t rwlock; //讀寫鎖
int num = 1;//讀操作,其他線程允許讀操作,卻不允許寫操作
void *fun1(void *arg)
{while (1){pthread_rwlock_rdlock(&rwlock);printf("read num first===%d\n", num);pthread_rwlock_unlock(&rwlock);sleep(1);}
}//讀操作,其他線程允許讀操作,卻不允許寫操作
void *fun2(void *arg)
{while (1){pthread_rwlock_rdlock(&rwlock);printf("read num second===%d\n", num);pthread_rwlock_unlock(&rwlock);sleep(2);}
}//寫操作,其它線程都不允許讀或寫操作
void *fun3(void *arg)
{while (1){pthread_rwlock_wrlock(&rwlock);num++;printf("write thread first\n");pthread_rwlock_unlock(&rwlock);sleep(2);}
}//寫操作,其它線程都不允許讀或寫操作
void *fun4(void *arg)
{while (1){pthread_rwlock_wrlock(&rwlock);num++;printf("write thread second\n");pthread_rwlock_unlock(&rwlock);sleep(1);}
}int main()
{pthread_t ptd1, ptd2, ptd3, ptd4;pthread_rwlock_init(&rwlock, NULL);//初始化一個讀寫鎖//創建線程pthread_create(&ptd1, NULL, fun1, NULL);pthread_create(&ptd2, NULL, fun2, NULL);pthread_create(&ptd3, NULL, fun3, NULL);pthread_create(&ptd4, NULL, fun4, NULL);//等待線程結束,回收其資源pthread_join(ptd1, NULL);pthread_join(ptd2, NULL);pthread_join(ptd3, NULL);pthread_join(ptd4, NULL);pthread_rwlock_destroy(&rwlock);//銷毀讀寫鎖return 0;
}
結果:
條件變量
條件變量概述
與互斥鎖不同,條件變量是用來等待而不是用來上鎖的,條件變量本身不是鎖!
條件變量用來自動阻塞一個線程,直到某特殊情況發生為止。通常條件變量和互斥鎖同時使用。
條件變量的兩個動作:
-
條件不滿, 阻塞線程
-
當條件滿足, 通知阻塞的線程開始工作
條件變量的類型: pthread_cond_t。
pthread_cond_init函數
#include <pthread.h>
int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr);
功能:
初始化一個條件變量
參數:
cond:指向要初始化的條件變量指針。
attr:條件變量屬性,通常為默認值,傳NULL即可
也可以使用靜態初始化的方法,初始化條件變量:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
返回值:
成功:0
失敗:非0錯誤號
pthread_cond_destroy函數
#include <pthread.h>
int pthread_cond_destroy(pthread_cond_t *cond);
功能:
銷毀一個條件變量
參數:
cond:指向要初始化的條件變量指針
返回值:
成功:0
失敗:非0錯誤號
pthread_cond_wait函數
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
功能:
阻塞等待一個條件變量
a) 阻塞等待條件變量cond(參1)滿足
b) 釋放已掌握的互斥鎖(解鎖互斥量)相當于pthread_mutex_unlock(&mutex);
a) b) 兩步為一個原子操作。
c) 當被喚醒,pthread_cond_wait函數返回時,解除阻塞并重新申請獲取互斥鎖pthread_mutex_lock(&mutex);
參數:
cond:指向要初始化的條件變量指針
mutex:互斥鎖
返回值:
成功:0
失敗:非0錯誤號
限時等待一個條件變量:
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex,
const struct?*restrict abstime);
功能:
限時等待一個條件變量
參數:
cond:指向要初始化的條件變量指針
mutex:互斥鎖
abstime:絕對時間
返回值:
? ?成功:0
? ?失敗:非0錯誤號
abstime補充說明:
struct timespec {
????????time_t tv_sec; /* seconds */ // 秒
????????long ? tv_nsec; /* nanosecondes*/ // 納秒
}
time_t cur = time(NULL); //獲取當前時間。
struct timespec t; //定義timespec 結構體變量t
t.tv_sec = cur + 1; // 定時1秒
pthread_cond_timedwait(&cond, &t);
pthread_cond_signal函數
喚醒至阻塞在條件變量上的線程
#include <pthread.h>
int pthread_cond_signal(pthread_cond_t *cond);
功能:
喚醒至少一個阻塞在條件變量上的線程
參數:
cond:指向要初始化的條件變量指針
返回值:
成功:0
失敗:非0錯誤號
喚醒全部阻塞在條件變量上的線程:
int pthread_cond_broadcast(pthread_cond_t *cond);
功能:
喚醒全部阻塞在條件變量上的線程
參數:
cond:指向要初始化的條件變量指針
返回值:
成功:0
失敗:非0錯誤號
生產者消費者條件變量模型
線程同步典型的案例即為生產者消費者模型,而借助條件變量來實現這一模型,是比較常見的一種方法。
假定有兩個線程,一個模擬生產者行為,一個模擬消費者行為。兩個線程同時操作一個共享資源(一般稱之為匯聚),生產向其中添加產品,消費者從中消費掉產品。
/*借助條件變量模擬 生產者-消費者 問題*/
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <stdio.h> /*鏈表作為公享數據,需被互斥量保護*/
struct msg { struct msg *next; int num;
}; struct msg *head; /* 靜態初始化 一個條件變量 和 一個互斥量*/
pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; void *consumer(void *p)
{ struct msg *mp; for (;;) { pthread_mutex_lock(&lock); while (head == NULL) { //頭指針為空,說明沒有節點 可以為if嗎 pthread_cond_wait(&has_product, &lock); // 解鎖,并阻塞等待 } mp = head; head = mp->next; //模擬消費掉一個產品 pthread_mutex_unlock(&lock); printf("-Consume %lu---%d\n", pthread_self(), mp->num); free(mp); sleep(rand() % 5); }
} void *producer(void *p)
{ struct msg *mp; for (;;) { mp = malloc(sizeof(struct msg)); mp->num = rand() % 1000 + 1; //模擬生產一個產品 printf("-Produce ---------------------%d\n", mp->num); pthread_mutex_lock(&lock); mp->next = head; //頭插法 head = mp; pthread_mutex_unlock(&lock); pthread_cond_signal(&has_product); //將等待在該條件變量上的一個線程喚醒 sleep(rand() % 5); }
} int main(int argc, char *argv[])
{ pthread_t pid, cid; srand(time(NULL)); pthread_create(&pid, NULL, producer, NULL); pthread_create(&cid, NULL, consumer, NULL); pthread_join(pid, NULL); pthread_join(cid, NULL); return 0;
}
結果:
一個生產者多個消費者模型
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h> void err_thread(int ret, char *str)
{ if (ret != 0) { fprintf(stderr, "%s:%s\n", str, strerror(ret)); pthread_exit(NULL); }
} struct msg { int num; struct msg *next;
}; struct msg *head; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 定義/初始化一個互斥量
pthread_cond_t has_data = PTHREAD_COND_INITIALIZER; // 定義/初始化一個條件變量 void *produser(void *arg)
{ while (1) { struct msg *mp = malloc(sizeof(struct msg)); mp->num = rand() % 1000 + 1; // 模擬生產一個數據` printf("--produce %d\n", mp->num); pthread_mutex_lock(&mutex); // 加鎖 互斥量 mp->next = head; // 寫公共區域 head = mp; pthread_mutex_unlock(&mutex); // 解鎖 互斥量 pthread_cond_signal(&has_data); // 喚醒阻塞在條件變量 has_data上的線程. sleep(rand() % 3); } return NULL;
} void *consumer(void *arg)
{ while (1) { struct msg *mp; pthread_mutex_lock(&mutex); // 加鎖 互斥量 while (head == NULL) { pthread_cond_wait(&has_data, &mutex); // 阻塞等待條件變量, 解鎖 } // pthread_cond_wait 返回時, 重寫加鎖 mutex mp = head; head = mp->next; pthread_mutex_unlock(&mutex); // 解鎖 互斥量 printf("---------consumer id = %lu :%d\n", pthread_self(), mp->num); free(mp); sleep(rand()%3); } return NULL;
} int main(int argc, char *argv[])
{ int ret; pthread_t pid, cid; srand(time(NULL)); ret = pthread_create(&pid, NULL, produser, NULL); // 生產者 if (ret != 0) err_thread(ret, "pthread_create produser error"); ret = pthread_create(&cid, NULL, consumer, NULL); // 消費者 if (ret != 0) err_thread(ret, "pthread_create consuer error"); ret = pthread_create(&cid, NULL, consumer, NULL); // 消費者 if (ret != 0) err_thread(ret, "pthread_create consuer error"); ret = pthread_create(&cid, NULL, consumer, NULL); // 消費者 if (ret != 0) err_thread(ret, "pthread_create consuer error"); pthread_join(pid, NULL); pthread_join(cid, NULL); return 0;
}
結果:
條件變量的優缺點
相較于mutex而言,條件變量可以減少競爭。
如直接使用mutex,除了生產者、消費者之間要競爭互斥量以外,消費者之間也需要競爭互斥量,但如果匯聚(鏈表)中沒有數據,消費者之間競爭互斥鎖是無意義的。
有了條件變量機制以后,只有生產者完成生產,才會引起消費者之間的競爭。提高了程序效率。
信號量
信號量概述
信號量廣泛用于進程或線程間的同步和互斥,信號量本質上是一個非負的整數計數器,它被用來控制對公共資源的訪問。
編程時可根據操作信號量值的結果判斷是否對公共資源具有訪問的權限,當信號量值大于 0 時,則可以訪問,否則將阻塞。
PV 原語是對信號量的操作,一次 P 操作使信號量減1,一次 V 操作使信號量加1。
信號量主要用于進程或線程間的同步和互斥這兩種典型情況。
信號量數據類型為:sem_t。
信號量用于互斥:
信號量用于同步:
sem_init函數
初始化信號量:
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
功能:
創建一個信號量并初始化它的值。一個無名信號量在被使用前必須先初始化。
參數:
sem:信號量的地址。
pshared:等于 0,信號量在線程間共享(常用);不等于0,信號量在進程間共享。
value:信號量的初始值。
返回值:
成功:0
失敗: - 1
sem_destroy函數
銷毀信號量:
#include <semaphore.h>
int sem_destroy(sem_t *sem);
功能:
刪除 sem 標識的信號量。
參數:
sem:信號量地址。
返回值:
成功:0
失敗: - 1
信號量P操作(減1)
int sem_wait(sem_t *sem):
#include <semaphore.h>
int sem_wait(sem_t *sem);
功能:
將信號量的值減 1。操作前,先檢查信號量(sem)的值是否為 0,若信號量為 0,此函數會阻塞,直到信號量大于 0 時才進行減 1 操作。
參數:
sem:信號量的地址。
返回值:
成功:0
失敗: - 1
int sem_trywait(sem_t *sem):
int sem_trywait(sem_t *sem);
- 以非阻塞的方式來對信號量進行減 1 操作。
- 若操作前,信號量的值等于 0,則對信號量的操作失敗,函數立即返回。
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout):
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
- 限時嘗試將信號量的值減 1
- abs_timeout:絕對時間
abs_timeout補充說明:
struct timespec {
????????time_t tv_sec; /* seconds */ // 秒
????????long ? tv_nsec; /* nanosecondes*/ // 納秒
}
time_t cur = time(NULL); //獲取當前時間。
struct timespec t; //定義timespec 結構體變量t
t.tv_sec = cur + 1; // 定時1秒
sem_timedwait(&cond, &t);
信號量V操作(加1)
#include <semaphore.h>
int sem_post(sem_t *sem);
功能:
將信號量的值加 1 并發出信號喚醒等待線程(sem_wait())。
參數:
sem:信號量的地址。
返回值:
成功:0
失敗:-1
獲取信號量的值
#include <semaphore.h>
int sem_getvalue(sem_t *sem, int *sval);
功能:
獲取 sem 標識的信號量的值,保存在 sval 中。
參數:
sem:信號量地址。
sval:保存信號量值的地址。
返回值:
成功:0
失敗:-1
程序示例
/*信號量實現 生產者 消費者問題*/ #include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <stdio.h>
#include <semaphore.h> #define NUM 5 int queue[NUM]; //全局數組實現環形隊列
sem_t blank_number, product_number; //空格子信號量, 產品信號量 void *producer(void *arg)
{ int i = 0; while (1) { sem_wait(&blank_number); //生產者將空格子數--,為0則阻塞等待 queue[i] = rand() % 1000 + 1; //生產一個產品 printf("----Produce---%d\n", queue[i]); sem_post(&product_number); //將產品數++ i = (i+1) % NUM; //借助下標實現環形 sleep(rand()%1); }
} void *consumer(void *arg)
{ int i = 0; while (1) { sem_wait(&product_number); //消費者將產品數--,為0則阻塞等待 printf("-Consume---%d\n", queue[i]); queue[i] = 0; //消費一個產品 sem_post(&blank_number); //消費掉以后,將空格子數++ i = (i+1) % NUM; sleep(rand()%3); }
} int main(int argc, char *argv[])
{ pthread_t pid, cid; sem_init(&blank_number, 0, NUM); //初始化空格子信號量為5, 線程間共享 -- 0 sem_init(&product_number, 0, 0); //產品數為0 pthread_create(&pid, NULL, producer, NULL); pthread_create(&cid, NULL, consumer, NULL); pthread_join(pid, NULL); pthread_join(cid, NULL); sem_destroy(&blank_number); sem_destroy(&product_number); return 0;
}
結果: