讀寫鎖、自旋鎖
概述:
在一些程序中存在讀者寫者問題,也就是說,對某些資源的訪問會 存在兩種可能的情況,一種是訪問必須是排它行的,就是獨占的意思,這稱作寫操作;
另一種情況就是訪問方式可以是共享的,就是說可以有多個線程同時去訪問某個資源,這種就稱作讀操作。這個問題模型是從對文件的讀寫操作中引申出來的。
讀寫鎖比起mutex具有更高的適用性,具有更高的並行性,可以有多個線程同時占用讀模式的讀寫鎖,但是只能有一個線程占用寫模式的讀寫鎖,讀寫鎖的三種狀態:
1.當讀寫鎖是寫加鎖狀態時,在這個鎖被解鎖之前,所有試圖對這個鎖加鎖的線程都會被阻塞
2.當讀寫鎖在讀加鎖狀態時,所有試圖以讀模式對它進行加鎖的線程都可以得到訪問權,但是以寫模式對它進行加鎖的線程將會被阻塞
3.當讀寫鎖在讀模式的鎖狀態時,如果有另外的線程試圖以寫模式加鎖,讀寫鎖通常會阻塞隨後的讀模式鎖的請求,這樣可以避免讀模式鎖長期占用,而等待的寫模式鎖請求則長期阻塞。
讀寫鎖最適用於對數據結構的讀操作次數多於寫操作的場合,因為,讀模式鎖定時可以共享,而寫模式鎖定時只能某個線程獨占資源,因而,讀寫鎖也可以叫做個共享-獨占鎖。
處理讀者-寫者問題的兩種常見策略是強讀者同步(strong reader synchronization)和強寫者同步(strong writer synchronization). 在強讀者同步中,總是給讀者更高的優先權,只要寫者當前沒有進行寫操作,
讀者就可以獲得訪問權限;而在強寫者同步中,則往往將優先權交付給寫者,而讀者只能等到所有正在等待的或者是正在執行的寫者結束以後才能執行。關於讀者-寫者模型中,
由於讀者往往會要求查看最新的信息記錄,所以航班訂票系統往往會使用強寫者同步策略,而圖書館查閱系統則采用強讀者同步策略。
讀寫鎖機制是由posix提供的,如果寫者沒有持有讀寫鎖,那麽所有的讀者多可以持有這把鎖,而一旦有某個寫者阻塞在上鎖的時候,那麽就由posix系統來決定是否允許讀者獲取該鎖。
在創建互斥鎖的時候有兩種方式,①:定義一個全局變量pthread_rwlock_t。②利用malloc進行動態分配。切記不可定義成局部變量,讀寫鎖是要被多個線程訪問的,
如果讀寫鎖是被定義在一個線程內部的局部變量,當出了作用域被另一個線程訪問時,這個鎖就變成了無效的,還有可能引起無法預料的後果。
基本操作函數
1.初始化和銷毀讀寫鎖
對於讀寫鎖變量的初始化可以有兩種方式,一種是通過給一個靜態分配的讀寫鎖賦予常值PTHREAD_RWLOCK_INITIALIZER來初始化它,另一種方法就是通過調用pthread_rwlock_init()來動態的初始化。
而當某個線程不再需要讀寫鎖的時候,可以通過調用
pthread_rwlock_destroy來銷毀該鎖。函數原型如下:
#include
int pthread_rwlock_init(pthread_rwlock_t *rwptr, const pthread_rwlockattr_t *attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwptr);
這兩個函數如果執行成功均返回0,如果出錯則返回錯誤碼。
在釋放某個讀寫鎖占用的內存之前,要先通過pthread_rwlock_destroy對讀寫鎖進行清理,釋放由pthread_rwlock_init所分配的資源。
在初始化某個讀寫鎖的時候,如果屬性指針attr是個空指針的話,表示默認的屬性;如果想要使用非默認屬性,則要使用到下面的兩個函數:
#include
int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);
int pthread_rwlockattr_destroy(pthread_rwlockatttr_t *attr);
這兩個函數同樣的,如果執行成功返回0,失敗返回錯誤碼。
這裏還需要說明的是,當初始化讀寫鎖完畢以後呢,該鎖就處於一個非鎖定狀態。
數據類型為pthread_rwlockattr_t的某個屬性對象一旦初始化了,就可以通過不同的函數調用來啟用或者是禁用某個特定的屬性。
2.獲取和釋放讀寫鎖
讀寫鎖的數據類型是pthread_rwlock_t,如果這個數據類型中的某個變量是靜態分配的,那麽可以通過給它賦予常值PTHREAD_RWLOCK_INITIALIZAR來初始化它。pthread_rwlock_rdlock()用來獲取讀出鎖,
如果相應的讀出鎖已經被某個寫入者占有,那麽就阻塞調用線程。pthread_rwlock_wrlock()用來獲取一個寫入鎖,如果相應的寫入鎖已經被其它寫入者或者一個或多個讀出者占有,那麽就阻塞該調用線程;
pthread_rwlock_unlock()用來釋放一個讀出或者寫入鎖。函數原型如下:
#include
int pthread_rwlock_rdlock(pthread_rwlock_t *rwptr);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwptr);
int pthread_rwlock_unlock(pthread_rwlock_t *rwptr);
這三個函數若調用成功則返回0,失敗就返回錯誤碼。要註意的是其中獲取鎖的兩個函數的操作都是阻塞操作,也就是說獲取不到鎖的話,那麽調用線程不是立即返回,而是阻塞執行。有寫情況下,
這種阻塞式的獲取所得方式可能不是很適用,所以,接下來引入兩個采用非阻塞方式獲取讀寫鎖的函數pthread_rwlock_tryrdlock()和pthread_rwlock_trywrlock(),非阻塞方式下獲取鎖的時候,如果不能馬上獲取到,
就會立即返回一個EBUSY錯誤,而不是把調用線程投入到睡眠等待。函數原型如下:
#include
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwptr);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwptr);
同樣地,這兩個函數調用成功返回0,失敗返回錯誤碼。
參考代碼
#include <stdio.h> #include <pthread.h> pthread_rwlock_t lock; int data; void *thread_write() { while(1) { usleep(50000); if(pthread_rwlock_wrlock(&lock) == 0) { data++; printf("writer a num: %d\n",data); pthread_rwlock_unlock(&lock); } else { printf("@\n@\n"); } } } void *thread_read() { while(1) { sleep(1); if(pthread_rwlock_rdlock(&lock) == 0) { printf("readr a num: %d\n",data); pthread_rwlock_unlock(&lock); } else { printf("@\n@\n"); } } } int main(void) { pthread_rwlock_init(&lock,NULL); pthread_t reader,writer; pthread_create(&reader,NULL,thread_read,NULL); pthread_create(&writer,NULL,thread_write,NULL); pthread_join(reader,NULL); pthread_join(writer,NULL); pthread_rwlock_destroy(&lock); return 0; }
參考:http://blog.chinaunix.net/uid-27177626-id-3791049.html
http://blog.csdn.net/KOwzb/article/details/72869352?locationNum=2&fps=1
http://www.cnblogs.com/Anker/archive/2013/01/09/2853137.html(重點閱讀)
http://www.cnblogs.com/huangwei/archive/2010/05/19/1739659.html(重點閱讀)
讀寫鎖、自旋鎖