讀寫鎖和互斥量(互斥鎖)很類似,是另一種線程同步機制,但不屬於POSIX標准,可以用來同步同一進程中的各個線程。當然如果一個讀寫鎖存放在多個進程共享的某個內存區中,那麼還可以用來進行進程間的同步, 互斥量要麼是鎖住狀態要麼是不加鎖狀態,而且一次只有一個線程可以對其加鎖。讀寫鎖可以有三種狀態:讀模式下的加鎖狀態,寫模式下的加鎖狀態,不加鎖狀態。
一次只有一個線程可以占有寫模式的讀寫鎖,但是多個線程可以同時占有讀模式的讀寫鎖。
當讀寫鎖是寫加鎖狀態時,在這個鎖被解鎖之前,所有試圖對這個鎖加鎖(讀或寫)的線程都會被阻塞。
當讀寫鎖在讀加鎖狀態時,所有試圖以讀模式對它加鎖的線程都可以得到訪問權,但是如果線程希望以寫模式對此鎖進行加鎖,它必須阻塞直到所有的線程釋放讀鎖。
當讀寫鎖在讀加鎖狀態時,如果有另外的線程試圖以寫模式加鎖,讀寫鎖通常會阻塞隨後的讀模式鎖請求。(後面有代碼驗證發現ubuntu 10.04系統下,不會阻塞隨後的讀模式的請求,最終導致請求寫鎖的線程餓死狀態)這樣可以避免讀模式鎖長期占有,而等待的寫模式鎖請求一直得不到滿足,出現餓死情況。後面會測試ubuntu 10.04系統的情況。(注意前篇關於記錄鎖fcntl中,ubuntu 10.04系統下,進程擁有讀鎖,然後優先處理後面的讀鎖,再處理寫鎖,導致寫鎖出現餓死)
讀寫鎖也稱為共享-獨占(shared-exclusive)鎖,當讀寫鎖以讀模式加鎖時,它是以共享模式鎖住,當以寫模式加鎖時,它是以獨占模式鎖住。讀寫鎖非常適合讀數據的頻率遠大於寫數據的頻率從的應用中。這樣可以在任何時刻運行多個讀線程並發的執行,給程序帶來了更高的並發度。
需要提到的是:讀寫鎖到目前為止仍然不是屬於POSIX標准。
1讀寫鎖的初始化和銷毀
#include <pthread.h> int pthread_rwlock_init (pthread_rwlock_t *rwlock,const pthread_rwlockattr_t *attr); int pthread_rwlock_destroy (pthread_rwlock_t *rwlock); 返回值:成功返回0,否則返回錯誤代碼
與互斥量一樣,讀寫鎖在使用之前必須初始化,在釋放它們底層的內存前必須銷毀。
上面兩個函數分別由於讀寫鎖的初始化和銷毀。和互斥量,條件變量一樣,如果讀寫鎖是靜態分配的,可以通過常量進行初始化,如下:
<span style="font-size:14px;">pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;</span>
也可以通過pthread_rwlock_init()進行初始化。對於動態分配的讀寫鎖由於不能直接賦值進行初始化,只能通過這種方式進行初始化。pthread_rwlock_init()第二個參數是讀寫鎖的屬性,如果采用默認屬性,可以傳入空指針NULL。
那麼當不在需要使用時及釋放(自動或者手動)讀寫鎖占用的內存之前,需要調用pthread_rwlock_destroy()進行銷毀讀寫鎖占用的資源。
2讀寫鎖的使用
/* 讀模式下加鎖 */ int pthread_rwlock_rdlock (pthread_rwlock_t *rwlock); /* 非阻塞的讀模式下加鎖 */ int pthread_rwlock_tryrdlock (pthread_rwlock_t *rwlock); /* 限時等待的讀模式加鎖 */ int pthread_rwlock_timedrdlock (pthread_rwlock_t *rwlock,const struct timespec *abstime); /* 寫模式下加鎖 */ int pthread_rwlock_wrlock (pthread_rwlock_t *rwlock); /* 非阻塞的寫模式下加鎖 */ int pthread_rwlock_trywrlock (pthread_rwlock_t *rwlock); /* 限時等待的寫模式加鎖 */ int pthread_rwlock_timedwrlock (pthread_rwlock_t *rwlock,const struct timespec *abstime); /* 解鎖 */ int pthread_rwlock_unlock (pthread_rwlock_t *rwlock); 返回值:成功返回0,否則返回錯誤代碼
(1)pthread_rwlock_rdlock()系列函數
pthread_rwlock_rdlock()用於以讀模式即共享模式獲取讀寫鎖,如果讀寫鎖已經被某個線程以寫模式占用,那麼調用線程就被阻塞。如果讀寫鎖已經被某個線程以寫模式占用,那麼調用線程將獲得讀鎖。如果讀寫鎖未沒有被占有,但有多個寫鎖正在等待該鎖時,調用線程現在試圖獲取讀鎖,是否能獲取該鎖是不確定的。在實現讀寫鎖的時候可以對共享模式下鎖的數量進行限制(目前不知如何限制)。
pthread_rwlock_tryrdlock()和pthread_rwlock_rdlock()的唯一區別就是,在無法獲取讀寫鎖的時候,調用線程不會阻塞,會立即返回,並返回錯誤代碼EBUSY。
針對未初始化的讀寫鎖調用pthread_rwlock_rdlock/pthread_rwlock_tryrdlock,則結果是不確定的。
pthread_rwlock_timedrdlock()是限時等待讀模式加鎖,時間參數struct timespec * abstime也是絕對時間。
(2)pthread_rwlock_wrlock()系列函數
pthread_rwlock_wrlock()用於寫模式即獨占模式獲取讀寫鎖,如果讀寫鎖已經被其他線程占用,不論是以共享模式還是獨占模式占用,調用線程都會進入阻塞狀態。
pthread_rwlock_trywrlock()在無法獲取讀寫鎖的時候,調用線程不會進入睡眠,會立即返回,並返回錯誤代碼EBUSY。
針對未初始化的讀寫鎖調用pthread_rwlock_wrlock/pthread_rwlock_trywrlock,則結果是不確定的。
pthread_rwlock_timedwrlock()是限時等待寫模式加鎖。
(3)pthread_rwlock_unlock()
無論以共享模式還是獨占模式獲得的讀寫鎖,都可以通過調用pthread_rwlock_unlock()函數進行釋放該讀寫鎖。
針對未初始化的讀寫鎖調用pthread_rwlock_unlock,則結果是不確定的。
注意:當讀寫鎖以讀模式被占有N次,即調用pthread_rwlock_rdlock() N次,且成功。則必須調用N次pthread_rwlock_unlock()才能執行匹配的解鎖操作。
示例代碼:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <pthread.h> #include <errno.h> #define MAXDATA 1024 #define MAXREDER 100 #define MAXWRITER 100 struct { pthread_rwlock_t rwlock; //讀寫鎖 char datas[MAXDATA]; //共享數據域 }shared = { PTHREAD_RWLOCK_INITIALIZER }; void *reader(void *arg); void *writer(void *arg); int main(int argc,char *argv[]) { int i,readercount,writercount; pthread_t tid_reader[MAXREDER],tid_writer[MAXWRITER]; if(argc != 3) { printf("usage : <reader_writer> #<readercount> #<writercount>\n"); exit(0); } readercount = atoi(argv[1]); //讀者個數 writercount = atoi(argv[2]); //寫者個數 pthread_setconcurrency(readercount+writercount); for(i=0;i<writercount;++i) pthread_create(&tid_writer[i],NULL,writer,NULL); sleep(1); //等待寫者先執行 for(i=0;i<readercount;++i) pthread_create(&tid_reader[i],NULL,reader,NULL); //等待線程終止 for(i=0;i<writercount;++i) pthread_join(tid_writer[i],NULL); for(i=0;i<readercount;++i) pthread_join(tid_reader[i],NULL); exit(0); } void *reader(void *arg) { pthread_rwlock_rdlock(&shared.rwlock); //獲取讀出鎖 if( pthread_rwlock_rdlock(&shared.rwlock) ==0 ) //獲取讀出鎖 printf("pthread_rwlock_rdlock OK\n"); printf("Reader begins read message.\n"); printf("Read message is: %s\n",shared.datas); pthread_rwlock_unlock(&shared.rwlock); //釋放鎖 if( pthread_rwlock_unlock(&shared.rwlock) != 0 ); printf("pthread_rwlock_unlock fail\n"); return NULL; } void *writer(void *arg) { char datas[MAXDATA]; pthread_rwlock_wrlock(&shared.rwlock); //獲取寫鎖 if( pthread_rwlock_wrlock(&shared.rwlock) != 0)//再次獲取寫鎖 perror("pthread_rwlock_wrlock"); if( pthread_rwlock_rdlock(&shared.rwlock) !=0 ) //獲取讀出鎖 perror("pthread_rwlock_rdlock"); printf("Writers begings write message.\n"); sleep(1); printf("Enter the write message: \n"); scanf("%s",datas); //寫入數據 strcat(shared.datas,datas); pthread_rwlock_unlock(&shared.rwlock); //釋放鎖 return NULL; }
運行結果:
huangcheng@ubuntu:~$ gcc 2.c -lpthread huangcheng@ubuntu:~$ ./a.out 5 3 pthread_rwlock_wrlock: Success pthread_rwlock_rdlock: Success Writers begings write message. Enter the write message: hu pthread_rwlock_wrlock: Success pthread_rwlock_rdlock: Success Writers begings write message. Enter the write message: 1 pthread_rwlock_wrlock: Success pthread_rwlock_rdlock: Success Writers begings write message. Enter the write message: 2 pthread_rwlock_rdlock OK Reader begins read message. Read message is: hu12 pthread_rwlock_unlock fail pthread_rwlock_rdlock OK Reader begins read message. Read message is: hu12 pthread_rwlock_unlock fail pthread_rwlock_rdlock OK Reader begins read message. Read message is: hu12 pthread_rwlock_unlock fail pthread_rwlock_rdlock OK Reader begins read message. Read message is: hu12 pthread_rwlock_unlock fail pthread_rwlock_rdlock OK Reader begins read message. Read message is: hu12 pthread_rwlock_unlock fail
結果說明:
(1)當一個線程獲得讀寫鎖的寫模式,其他線程試圖獲得該讀寫鎖的讀模式或者是寫模式,都將會阻塞,直到該線程釋放該讀寫鎖。
(2)當一個線程獲得讀寫鎖的寫模式,該線程試圖獲得該讀寫鎖的讀模式或寫模式,都會立即返回失敗,不會導致失敗。
(3)當一個線程獲得讀寫鎖的讀模式,如果該線程試圖獲得該讀寫鎖的讀模式,則返回成功,並在該線程釋放該讀寫鎖只需要釋放一次,第二次會釋放失敗。
(4)當一個線程獲得讀寫鎖的讀模式,且還沒有釋放該讀寫鎖,如果該線程試圖獲得該讀寫鎖的寫模式,將導致阻塞,直到該線程釋放該讀寫鎖。
(5)當一個線程獲得讀寫鎖的寫模式,該線程試圖釋放該寫鎖兩次,將導致不可預測的問題。
(6)當一個線程獲得讀寫鎖的寫模式或者讀模式,不能讀該讀寫鎖釋放兩次。即不管該線程獲得讀鎖幾次,都只需要釋放該讀寫鎖一次就OK。
3讀寫鎖的屬性設置
/* 初始化讀寫鎖屬性對象 */ int pthread_rwlockattr_init (pthread_rwlockattr_t *attr); /* 銷毀讀寫鎖屬性對象 */ int pthread_rwlockattr_destroy (pthread_rwlockattr_t *attr); /* 獲取讀寫鎖屬性對象在進程間共享與否的標識*/ int pthread_rwlockattr_getpshared (__const pthread_rwlockattr_t *attr,int *pshared); /* 設置讀寫鎖屬性對象,標識在進程間共享與否 */ int pthread_rwlockattr_setpshared (pthread_rwlockattr_t *attr, int pshared); 返回值:成功返回0,否則返回錯誤代碼
pthread_rwlockattr_setpshared()函數的第二個參數pshared用於設定是否進程間共享,其值可以是PTHREAD_PROCESS_PRIVATE或PTHREAD_PROCESS_SHARED,後者是設置進程間共享。
查看本欄目更多精彩內容:http://www.bianceng.cn/OS/unix/
示例代碼:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <pthread.h> #include <errno.h> struct{ pthread_rwlock_t rwlock; int product; }sharedData = {PTHREAD_RWLOCK_INITIALIZER, 0}; void * produce(void *ptr) { int i; for ( i = 0; i < 5; ++i) { pthread_rwlock_wrlock(&sharedData.rwlock); sharedData.product = i; printf("produce:%d\n",i); pthread_rwlock_unlock(&sharedData.rwlock); sleep(1); } } void * consume1(void *ptr) { int i; for ( i = 0; i < 5;) { pthread_rwlock_rdlock(&sharedData.rwlock); printf("consume1:%d\n",sharedData.product); pthread_rwlock_unlock(&sharedData.rwlock); ++i; sleep(1); } } void * consume2(void *ptr) { int i; for ( i = 0; i < 5;) { pthread_rwlock_rdlock(&sharedData.rwlock); printf("consume2:%d\n",sharedData.product); pthread_rwlock_unlock(&sharedData.rwlock); ++i; sleep(1); } } int main() { pthread_t tid1, tid2, tid3; pthread_create(&tid1, NULL, produce, NULL); pthread_create(&tid2, NULL, consume1, NULL); pthread_create(&tid3, NULL, consume2, NULL); void *retVal; pthread_join(tid1, &retVal); pthread_join(tid2, &retVal); pthread_join(tid3, &retVal); return 0; }
運行結果:
huangcheng@ubuntu:~$ ./a.out consume2:0 consume1:0 produce:0 consume2:0 consume1:0 produce:1 consume2:1 consume1:1 produce:2 consume2:2 consume1:2 produce:3 consume2:3 consume1:3 produce:4 huangcheng@ubuntu:~$
如果把consume1的解鎖注釋掉,如下:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <pthread.h> #include <errno.h> struct{ pthread_rwlock_t rwlock; int product; }sharedData = {PTHREAD_RWLOCK_INITIALIZER, 0}; void * produce(void *ptr) { int i; for ( i = 0; i < 5; ++i) { pthread_rwlock_wrlock(&sharedData.rwlock); sharedData.product = i; printf("produce:%d\n",i); pthread_rwlock_unlock(&sharedData.rwlock); sleep(1); } } void * consume1(void *ptr) { int i; for ( i = 0; i < 5;) { pthread_rwlock_rdlock(&sharedData.rwlock); printf("consume1:%d\n",sharedData.product); // pthread_rwlock_unlock(&sharedData.rwlock); ++i; sleep(1); } } void * consume2(void *ptr) { int i; for ( i = 0; i < 5;) { pthread_rwlock_rdlock(&sharedData.rwlock); printf("consume2:%d\n",sharedData.product); pthread_rwlock_unlock(&sharedData.rwlock); ++i; sleep(1); } } int main() { pthread_t tid1, tid2, tid3; pthread_create(&tid1, NULL, produce, NULL); pthread_create(&tid2, NULL, consume1, NULL); pthread_create(&tid3, NULL, consume2, NULL); void *retVal; pthread_join(tid1, &retVal); pthread_join(tid2, &retVal); pthread_join(tid3, &retVal); return 0; }
程序運行結果:
huangcheng@ubuntu:~$ ./a.out consume2:0 consume1:0 consume2:0 consume1:0 consume2:0 consume1:0 consume2:0 consume1:0 consume2:0 consume1:0 最後程序保持阻塞狀態
從執行結果可以看出Ubuntu 10.04提供的讀寫鎖函數是優先考慮等待讀模式占用鎖的線程,這種實現的一個很大缺陷就是出現寫入線程餓死的情況。
作者信息:csdn博客 ctthuangcheng