Linux下的多執行緒程式設計二(執行緒的同步與互斥)
一、什麼叫做執行緒的同步與互斥?為什麼需要同步與互斥?
1、同步與互斥
互斥:是指某一資源同時只允許一個訪問者對其進行訪問,具有唯一性和排它性。但互斥無法限制訪問者對資源的訪問順序,即訪問是無序的。
同步:是指在互斥的基礎上(大多數情況),通過其它機制實現訪問者對資源的有序訪問。在大多數情況下,同步已經實現了互斥,特別是所有寫入資源的情況必定是互斥的。少數情況是指可以允許多個訪問者同時訪問資源。
顯然,同步是一種更為複雜的互斥,而互斥是一種特殊的同步。
2、執行緒的同步與非同步
執行緒同步是多個執行緒同時訪問同一資源,等待資源訪問結束,浪費時間,效率低 。
執行緒非同步:訪問資源時在空閒等待時同時訪問其他資源,實現多執行緒機制 。非同步處理就是,你現在問我問題,我可以不回答你,等我用時間了再處理你這個問題。
同步資訊被立即處理 – 直到資訊處理完成才返回訊息控制代碼;
非同步資訊收到後將在後臺處理一段時間 – 而早在資訊處理結束前就返回訊息控制代碼。
3、多執行緒的同步與互斥的區別
假如把整條道路看成是一個【程序】的話,那麼馬路中間白色虛線分隔開來的各個車道就是程序中的各個【執行緒】了。
①這些執行緒(車道)共享了程序(道路)的公共資源(土地資源)。
②這些執行緒(車道)必須依賴於程序(道路),也就是說,執行緒不能脫離於程序而存在(就像離開了道路,車道也就沒有意義了)。
③這些執行緒(車道)之間可以併發執行(各個車道你走你的,我走我的),也可以互相同步(某些車道在交通燈亮時禁止繼續前行或轉彎,必須等待其它車道的車輛通行完畢)。
④這些執行緒(車道)之間依靠程式碼邏輯(交通燈)來控制執行,一旦程式碼邏輯控制有誤(死鎖,多個執行緒同時競爭唯一資源),那麼執行緒將陷入混亂,無序之中。
⑤這些執行緒(車道)之間誰先執行是未知的,只有在執行緒剛好被分配到CPU時間片(交通燈變化)的那一刻才能知道。
注:由於用於互斥的訊號量sem與所有的併發程序有關,所以稱之為公有訊號量。公有訊號量的值反映了公有資源的數量。只要把臨界區置於P(sem)和V(sem)之間,即可實現程序間的互斥。就象火車中的每節車廂只有一個衛生間,該車廂的所有旅客共享這個公有資源:衛生間,所以旅客間必須互斥進入衛生間,只要把衛生間放在P(sem)和V(sem)之間,就可以到達互斥的效果。
二、mutex(互斥量)
1、多執行緒的衝突舉例
我們知道,多個執行緒同時訪問共享資料時,可能會產生衝突。比如兩個執行緒都要把某個全域性變數增加1,這個操作在某平臺需要三條指令完成:
1> 從記憶體讀變數值到暫存器
2>暫存器的值加1
3>將暫存器的值寫回記憶體
假設兩個執行緒在多處理器平臺上同時執行這三條指令,則可能導致下圖所示的結果,最後變數只加了一次而非兩次。
資料二義性產生原因:不同的執行緒訪問同一份臨界資源,其次執行緒的操作不是原子操作。
為了驗證多執行緒同時訪問臨界資源產生的衝突與資料的二義性,建立兩個執行緒,各自把global增加5000次,正常情況下最後global應該等於10000,但事實上每次執行該程式的結果都不一樣,程式碼如下(為了使現象更容易觀察到,我們把上述三條指令做的事情用更多條指令來做):
1 /**************************************
2 *檔案說明:mutex.c
3 *作者:段曉雪
4 *建立時間:2017年05月29日 星期一 16時11分42秒
5 *開發環境:Kali Linux/g++ v6.3.0
6 ****************************************/
7 #include<stdio.h>
8 #include<stdlib.h>
9 #include<pthread.h>
10
11 int global = 0;//定義全域性變數
13 {
14 int i = 5000;//資料太小,執行緒發生衝突的概率比較小
15 //int i = 5000000;//1、增大迴圈次數,發生衝突較多
16 //while(i--)
17 //{
18 // global++;
19 //}
20 //2、增多執行緒切換:核心態---->使用者態,增大沖突概率
21 while(i--)
22 {
23 int tmp = global;
24 printf("%d\n",global);
25 global = tmp + 1;
26 }
27 //printf("\n");
28 return (void*)0;
29 }
30 int main()
31 {
32 pthread_t tid1;
33 pthread_t tid2;
34 pthread_create(&tid1,NULL,pthread_add,NULL);//建立執行緒1
35 pthread_create(&tid2,NULL,pthread_add,NULL);//建立執行緒2
36 pthread_join(tid1,NULL);//等待執行緒1
37 pthread_join(tid2,NULL);//等待執行緒2
38 printf("the global is %d\n",global);
39 return 0;
40 }
第一次執行結果—-5101:
第二次執行結果—-5091:
第三次執行結果—-5000:
3次執行結果均不一樣,足以說明多執行緒在同步訪問全域性變數global時發生了衝突。
2、解決多執行緒的衝突問題 —-引入互斥鎖
(1)互斥鎖:(Mutex,Mutual Exclusive Lock),獲得鎖的執行緒可以完成“讀-修改-寫”的操作,然後釋放鎖給其它執行緒,沒有獲得 鎖的執行緒只能等待而不能訪問共享資料,這樣“讀-修改-寫”三步操作組成一個原子操作,要麼都執行,要麼都不執行,不會執行到中間被打斷,也不會在其它處理器上並行做這個操作。
(2)互斥鎖的建立和銷燬:
Mutex用pthread_mutex_t型別的變量表示,可以這樣初始化和銷燬。
1>定義一把鎖
pthread_mutex_t lock;
2>互斥鎖lock的初始化
如果lock是區域性變數—可用初始化函式pthread_mutex_init初始化。
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
返回值:成功返回0,失敗返回錯誤號。
如果lock是靜態或全域性變數—可用 PTHREAD_MUTEX_INITIALIZER初始化 ,相當於用pthread_mutex_init初始化並且attr引數為NULL。
3>加鎖與解鎖
int pthread_mutex_trylock(pthread_mutex_t* mutex);//非阻塞式加鎖
int pthread_mutex_lock(pthread_mutex_t* mutex)//阻塞式加鎖
int pthread_mutex_unlock(pthread_mutex_t* mutex)//解鎖
如果一個執行緒既想獲得鎖,又不想掛起等待,可以呼叫pthread_mutex_trylock,如果Mutex已經被 另一個執行緒獲得,這個函式會失敗返回EBUSY,而不會使執行緒掛起等待。
Mutex的兩個基本操作lock和unlock是如何實現的呢?
假設Mutex變數 的值為1表示互斥鎖空閒,這時某個程序呼叫lock可以獲得鎖,而Mutex的值為0表示互斥鎖已經被某個執行緒獲得,其它執行緒再呼叫lock只能掛起等待。
4>銷燬鎖資源
int pthread_mutex_destroy(pthread_mutex_t* mutex);
返回值:成功返回0,失敗返回錯誤號。
(3)解決多執行緒的衝突問題
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
int global = 0;//定義全域性變數
//加入互斥鎖解決執行緒衝突
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;//定義全域性鎖並初始化
void* pthread_add(void* val)
{
int i = 5000;
while(i--)
{
pthread_mutex_lock(&lock);//加鎖
int tmp = global;
printf("%d\n",global);
global = tmp + 1;
pthread_mutex_unlock(&lock);//解鎖
}
return (void*)0;
}
int main()
{
pthread_t tid1;
pthread_t tid2;
pthread_create(&tid1,NULL,pthread_add,NULL);//建立執行緒1
pthread_create(&tid2,NULL,pthread_add,NULL);//建立執行緒2
pthread_join(tid1,NULL);//等待執行緒1
pthread_join(tid2,NULL);//等待執行緒2
printf("the global is %d\n",global);
pthread_mutex_destroy(&lock);//銷燬鎖資源
return 0;
}
第一次執行結果:
第二次執行結果:
第三次執行結果:
可見,加了互斥鎖之後,執行結果是正確的,解決了多執行緒的衝突問題。
(4)互斥鎖的加鎖和解鎖是原子的。
加鎖解鎖指令必須依賴於一條彙編指令:swap和exchange(原子操作)。大多數體系結構都提供了swap或exchange指令,該指令的作用是把暫存器和記憶體單元的資料相交換,由於只有一條指令,保證了原子性,即使是多處理器平臺,訪問記憶體的匯流排週期也有先後,一個處理器上的交換指令執行時另一個處理器的交換指令只能等待匯流排週期。
“掛起等待”和“喚醒等待執行緒”的操作如何實現?
每個Mutex有一個等待佇列,一個執行緒要在Mutex上掛起等待,首先在把自己加入等待佇列中,然後置執行緒狀態為睡眠,然後呼叫排程器函式切換到別的執行緒。一個執行緒要喚醒等待佇列中的其它執行緒,只需從等待佇列中取出一 項,把它的狀態從睡眠改為就緒,加入就緒佇列,那麼下次排程器函式執行時就有 可能切換到被喚醒的執行緒。
作業系統中同一時間可以有多個程序處於running狀態,需要把其PCB組織起來。
注意:互斥鎖的加入有可能引起“飢餓”和死鎖問題。
死鎖的概述和總結
三、Condition Variable (條件變數)
1、條件變數:描述某些資源就緒與否的狀態,為了實現同步而引入。
同步則是為了協調多執行緒之間的飢餓問題。
大多數情況下,同步是以互斥為前提的。少數情況可實現無鎖同步。
一個Condition Variable總是和一個Mutex搭配使用的。一個執行緒可以呼叫pthread_cond_wait在一個Condition Variable上阻塞等待,這個函式做以下三步操作:
1>釋放Mutex
2>阻塞等待
3>當被喚醒時,重新獲得Mutex並返回
2、條件變數的使用
1>建立條件變數condition
pthread_cond_t condition;
2>條件變數condition的初始化
如果condition是區域性變數—可用初始化函式pthread_mutex_init初始化。
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
返回值:成功返回0,失敗返回錯誤號。
如果condition是靜態或全域性變數—可用 PTHEAD_COND_INITIALIZER初始化 ,相當於用pthread_cond_init初始化並且attr引數為NULL。
3>操作條件變數
int pthread_cond_broadcast(pthread_cond_t* cond);//喚醒條件變數等待的所有執行緒
int pthread_cond_signal(pthread_cond_t* cond)//喚醒條件變數上等待的一個執行緒
int pthread_cond_wait(pthread_cond_t* cond)//解鎖互斥量並等待條件變數觸發
int pthread_cond_timewait(pthread_cond_t* cond,int abstime)//pthread_cond_wait,但可設定等待超時
pthread_cond_wait 自動解鎖互斥量(如同執行了 pthread_unlock_mutex),並等待條件變數觸發;
pthread_cond_timedwait與pthread_cond_wait,區別在於,如果達到或是超過所引用的引數*abstime,它將結束並返回錯誤ETIMEDOUT;
當呼叫pthread_cond_signal時一個在相同條件變數上阻塞的執行緒將被解鎖;
pthread_cond_broadcast,則將通知阻塞在這個條件變數上的所有執行緒。
4>銷燬條件變數
int pthread_cond_destroy(pthread_cond_t* cond);
返回值:成功返回0,失敗返回錯誤號。
3、條件變數的例項
生產者-消費者模型
生產者從單鏈表頭部生產節點,消費者從單鏈表頭部消費節點:
1 /**************************************
2 *檔案說明:list.c
3 *作者:段曉雪
4 *建立時間:2016年07月26日 星期二 10時59分31秒
5 *開發環境:Kali Linux/g++ v6.3.0
6 ****************************************/
7
8 #include<stdio.h>
9 #include<pthread.h>
10 #include<stdlib.h>
11 #include<unistd.h>
12
13 typedef struct list_node
14 {
15 struct list_node* _next;
16 int _data;
17 }Node,*pNode,**ppNode;
18
19 pNode head = NULL;//單鏈表的頭節點
20
21 static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;//定義鎖
22 static pthread_cond_t condition = PTHREAD_COND_INITIALIZER;//定義條件變數
23
24 static pNode CreateNode(int data)//建立連結串列的新節點
25 {
26 pNode newNode = (pNode)malloc(sizeof(Node));
27 if(NULL == newNode)
28 {
29 perror("malloc error");
30 return NULL;
31 }
32 newNode->_data = data;
33 newNode->_next = NULL;
34 return newNode;
35 }
36
37 static void FreeNode(pNode node)//刪除節點
38 {
39 if(node != NULL)
40 {
41 free(node);
42 node = NULL;
43 }
44 }
45
46 void InitList(ppNode list)//單鏈表的初始化
47 {
48 *list = CreateNode(0);
49 }
50
51 void PushFront(pNode node,int data)//頭插
52 {
53 pNode newNode = CreateNode(data);
54 newNode->_next = node->_next;
55 node->_next = newNode;
56 }
57
58 int Empty(pNode node)//判斷連結串列是否為空
59 {
60 return node->_next==NULL ? 1:0;
61 }
62
63 void PopFront(pNode node,int* data)//頭刪
64 {
65 if(!Empty(node))
66 {
67 pNode del = node->_next;
68 node->_next = node->_next->_next;
69 *data = del->_data;
70 FreeNode(del);
71 }
72 }
73
74 void ShowList(pNode node)//列印連結串列
75 {
76 if(Empty(node))
77 {
78 printf("list empty!\n");
79 }
80 else
81 {
82 pNode cur = node->_next;
83 while(cur)
84 {
85 printf("%d->",cur->_data);
86 cur = cur->_next;
87 }
88 printf("NULL\n");
89 }
90 }
91
92 void DestroyList(pNode node)//銷燬連結串列
93 {
94 int data = 0;
95 while(!Empty(node))
96 {
97 PopFront(node,&data);
98 }
99 FreeNode(node);
100 }
101
102 void* product(void* arg)//生產者控制代碼
103 {
104 int data = 0;
105 while(1)
106 {
107 sleep(1);
108 data = rand() % 1234;
109 pthread_mutex_lock(&lock);//加鎖
110 PushFront(head,data);
111 pthread_mutex_unlock(&lock);//解鎖
112 pthread_cond_signal(&condition);//喚醒等待的程序
113 printf("product:%d\n",data);
114 }
115 }
116
117 void* consumer(void* arg)//消費者控制代碼
118 {
119 int data = 0;
120 while(1)
121 {
122 pthread_mutex_lock(&lock);//加鎖
123 while(Empty(head))
124 {
125 pthread_cond_wait(&condition,&lock);//如果為空,釋放鎖,等待被喚醒之後獲得鎖
126 }
127 PopFront(head,&data);
128 pthread_mutex_unlock(&lock);//解鎖
129 printf("consumer:%d\n",data);
130 }
131 }
132
133 int main()
134 {
135 InitList(&head);//初始化單鏈表
136 pthread_t tid1,tid2;
137 pthread_create(&tid1,NULL,product,NULL);//生產者執行緒
138 pthread_create(&tid2,NULL,consumer,NULL);//消費者執行緒
139 pthread_join(tid1,NULL);//生產者執行緒等待
140 pthread_join(tid2,NULL);//消費者執行緒等待
141
142 DestroyList(head);
143 pthread_mutex_destroy(&lock);//銷燬鎖
144 pthread_cond_destroy(&condition);//銷燬條件變數
145
146 return 0;
147 }
執行結果:
四、Semaphore(訊號量)
1、什麼是訊號量
為了防止出現因多個程式同時訪問一個共享資源而引發的一系列問題,我們需要一種方法,它可以通過生成並使用令牌來授權,在任一時刻只能有一個執行執行緒訪問程式碼的臨界區域。臨界區域是指執行資料更新的程式碼需要獨佔式地執行。而訊號量就可以提供這樣的一種訪問機制,讓一個臨界區同一時間只有一個執行緒在訪問它,也就是說訊號量是用來調協程序對共享資源的訪問的。
訊號量是一個特殊的變數,程式對其訪問都是原子操作,且只允許對它進行等待(即P(訊號變數))和傳送(即V(訊號變數))資訊操作。最簡單的訊號量是隻能取0和1的變數,這也是訊號量最常見的一種形式,叫做二進位制訊號量。而可以取多個正整數的訊號量被稱為通用訊號量。這裡主要討論二進位制訊號量。
2、訊號量的工作原理
由於訊號量只能進行兩種操作等待和傳送訊號,即P(sv)和V(sv),他們的行為是這樣的:
P(sv):如果sv的值大於零,就給它減1;如果它的值為零,就掛起該程序的執行
V(sv):如果有其他程序因等待sv而被掛起,就讓它恢復執行,如果沒有程序因等待sv而掛起,就給它加1.
舉個例子,就是兩個程序共享訊號量sv,一旦其中一個程序執行了P(sv)操作,它將得到訊號量,並可以進入臨界區,使sv減1。而第二個程序將被阻止進入臨界區,因為當它試圖執行P(sv)時,sv為0,它會被掛起以等待第一個程序離開臨界區域並執行V(sv)釋放訊號量,這時第二個程序就可以恢復執行。
3、Linux的訊號量機制
Linux提供了一組精心設計的訊號量介面來對訊號進行操作,它們不只是針對二進位制訊號量,下面將會對這些函式進行介紹,但請注意,這些函式都是用來對成組的訊號量值進行操作的。它們宣告在標頭檔案semphore.h中。
4、訊號量的使用
1>建立訊號量sem
sem_t sem;
2>初始化訊號量
int sem_init(sem_t *sem, int pshared, unsigned int value);
pshared引數為0表示訊號量用於同一程序的執行緒間同步。
3>操作訊號量
P操作:
int sem_wait(sem_t *sem);//訊號量的P操作
int sem_trywait(sem_t *sem);//訊號量的P操作(非阻塞版本)
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);//abs_timeout是UTC時間戳
sem_wait()可以獲得資源(P操作),使semaphore的值減1,如果調⽤用sem_wait()時
semaphore的值 已經是0,則掛起等待
V操作:
int sem_post(sem_t *sem);//訊號量的V操作
呼叫sem_post() 可以釋放資源(V操作),使semaphore 的值加1,同時喚醒掛起等待的執行緒。
4> 銷燬訊號量
int sem_destroy(sem_t *sem);
5、程式碼例項
基於環形佇列的生產者消費者模型:
1 /**************************************
2 *檔案說明:ring_queue.c
3 *作者:段曉雪
4 *建立時間:2017年06月07日 星期三 17時00分44秒
5 *開發環境:Kali Linux/g++ v6.3.0
6 ****************************************/
7
8 #include<stdio.h>
9 #include<unistd.h>
10 #include<pthread.h>
11 #include<semaphore.h>
12 #include<stdlib.h>
13
14 int ring[64];
15 sem_t blank_sem;//生產者關心的格子訊號量
16 sem_t data_sem;//消費者關心的資料訊號量
17
18 void* producter(void* arg)//生產者控制代碼
19 {
20 int step = 0;
21 int data = 0;
22 while(1)
23 {
24 sleep(1);
25 sem_wait(&blank_sem);//格子數-1
26 data = rand() % 123;
27 ring[step] = data;
28 ++step;
29 step %= 64;
30 printf("producter done:%d\n",data);
31 sem_post(&data_sem);//資料+1
32 }
33 }
34 void* consumer(void* arg)//消費者控制代碼
35 {
36 int step = 0;
37 int data = 0;
38 while(1)
39 {
40 sem_wait(&data_sem);//資料-1
41 data = ring[step];
42 ++step;
43 step %= 64;
44 printf("consumer done:%d\n",data);
45 sem_post(&blank_sem);//格子+1
46 }
47 }
48
49 int main()
50 {
51 sem_init(&blank_sem,0,64);//格子訊號量的初始化
52 sem_init(&data_sem,0,0);//資料訊號量的初始化
53
54 pthread_t tid1,tid2;
55 pthread_create(&tid1,NULL,producter,NULL);//生產者執行緒
56 pthread_create(&tid2,NULL,consumer,NULL);//消費者執行緒
57 pthread_join(tid1,NULL);
58 pthread_join(tid2,NULL);
59
60 sem_destroy(&blank_sem);
61 sem_destroy(&data_sem);
62 return 0;
63 }
執行結果:
五、rwlock(讀寫鎖)
1、讀寫鎖的概念
在編寫多執行緒的時候,有一種情況是〸分常見的。那就是,有些公共資料修改的機會比較少。相比較改寫,它們讀的機會反而高的多。通常而言,在讀的過程中,往往伴隨著查詢的操作,中間耗時很長。給這種程式碼段加鎖,會極大地降低我們程式的效率。
為了解決上述問題,我們引入了讀寫鎖的概念。
讀寫鎖實際是一種特殊的自旋鎖,它把對共享資源的訪問者劃分成讀者和寫者,讀者只對共享資源進行讀訪問,寫者則需要對共享資源進行寫操作。這種鎖相對於自旋鎖而言,能提高併發性,因為在多處理器系統中,它允許同時有多個讀者來訪問共享資源,最大可能的讀者數為實際的邏輯CPU數。寫者是排他性的,一個讀寫鎖同時只能有一個寫者或多
個讀者(與CPU數相關),但不能同時既有讀者又有寫者。
2、讀寫鎖的”三二一“記憶原則
三二一原則:三種關係兩個角色一個交易場所
寫者與寫者:互斥
讀者與寫者:同步、互斥
讀者與讀者:沒關係
讀者和寫者同步(讀寫)過程:要麼讀者優先,要麼寫者優先—〉讀寫鎖
讀的次數非常多而寫的次數非常少適用於讀寫鎖。
單CPU在任何時刻只能有一個執行緒在執行。
併發:在一個時間段內多個執行緒交替輪流執行。
並行:在多CPU機器中能夠實現多執行緒真正意義上的同時執行。
自旋鎖:當申請鎖資源的執行緒佔用臨界資源的時間比較短,一直詢問不進行掛起的鎖。(讀寫鎖)
掛起等待鎖:二元訊號量,互斥鎖。
3、讀寫鎖的使用
1>定義讀寫鎖
pthread_rwlock_t rwlock;
2>初始化讀寫鎖
靜態或全域性的讀寫鎖可用巨集PTHREAD_RWLOCK_INITIALIZER初始化,相當於pthread_rwlock_init中的attr為NULL。
區域性定義的讀寫鎖需要用函式初始化:
int pthread_rwlock_init(pthread_rwlock_t *rwptr, const pthread_rwlockattr_t *attr);
如果執行成功返回0,失敗返回錯誤碼。
3> 讀寫鎖的操作
int pthread_rwlock_rdlock(pthread_rwlock_t *rwptr);//以阻塞方式獲取讀出鎖
int pthread_rwlock_wrlock(pthread_rwlock_t *rwptr);//以阻塞方式獲取寫入鎖
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwptr);//以非阻塞方式獲取讀出鎖
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwptr);//以非阻塞方式獲取寫入鎖
int pthread_rwlock_unlock(pthread_rwlock_t *rwptr);//釋放讀出鎖或寫入鎖
若呼叫成功則返回0,失敗就返回錯誤碼。
4>銷燬讀寫鎖
int pthread_rwlockattr_destroy(pthread_rwlockatttr_t *attr);
如果執行成功返回0,失敗返回錯誤碼。
4、讀寫鎖的例項
1 /**************************************
2 *檔案說明:rwlock.c
3 *作者:段曉雪
4 *建立時間:2017年06月08日 星期四 11時15分00秒
5 *開發環境:Kali Linux/g++ v6.3.0
6 ****************************************/
7
8 #include<stdio.h>
9 #include<pthread.h>
10 #include<unistd.h>
11
12 pthread_rwlock_t rwlock;//定義一把讀寫鎖
13 int buf = 0;
14
15void* reader(void* arg)
16 {
17 pthread_detach(pthread_self());//執行緒分離
18 while(1)
19 {
20 //try lock by reader
21 if(pthread_rwlock_trywrlock(&rwlock) != 0)//讀者獲取鎖資源失敗
22 {
23 printf("writer is writing,reader waiting...\n");
24 }
25 else
26 {
27 printf("reader is %lu,value is %d\n",pthread_self(),buf);
28 pthread_rwlock_unlock(&rwlock);//讀者讀完資料之後釋放鎖資源
29 }
30 sleep(2);
31 }
32 }
33
34 void* writer(void* arg)//寫者控制代碼
35 {
36 pthread_detach(pthread_self());//執行緒分離
37 while(1)
38 {
39 //try lock by writer
40 if(pthread_rwlock_tryrdlock(&rwlock) != 0)//獲取鎖資源失敗
41 {
42 printf("reader is reading,writer waiting...\n");
43 sleep(1);
44 }
45 else
46 {
47 buf++;//write
48 printf("writer is %lu,the value is %d\n",pthread_self(),buf);
49 pthread_rwlock_unlock(&rwlock);//寫者寫完之後釋放鎖資源
50 }
51 sleep(2);
52 }
53 }
54
55 int main()
56 {
57 pthread_rwlock_init(&rwlock,NULL);//初始化鎖
58 pthread_t read,write;
59 int i = 0;
60 for(; i < 2; ++i)//建立2個讀者執行緒
61 {
62 pthread_create(&read,NULL,reader,NULL);
63 }
64 for(i = 0;i < 3; ++i)//建立3個寫者執行緒
65 {
66 pthread_create(&write,NULL,writer,NULL);
67 }
68 for(i = 0; i < 2; ++i)//對讀者執行緒進行等待
69 {
70 pthread_join(read,NULL);
71 }
72 for(i = 0;i < 3; ++i)//對寫者執行緒進行等待
73 {
74 pthread_join(write,NULL);
75 }
76 pthread_rwlock_destroy(&rwlock);//銷燬讀寫鎖
77 return 0;
78 }
執行結果: