標籤:
1. 綜述
在一些程式中存在讀者寫者問題,也就是說,對某些資源的訪問會存在兩種可能的情況,一種是訪問必須是排它行的,就是獨佔的意思,這稱作寫操作;另一種情況就是訪問方式可以是共用的,就是說可以有多個線程同時去訪問某個資源,這種就稱作讀操作。這個問題模型是從對檔案的讀寫操作中引申出來的。
讀寫鎖比起mutex具有更高的適用性,具有更高的並行性,可以有多個線程同時佔用讀模式的讀寫鎖,但是只能有一個線程佔用寫入模式的讀寫鎖,讀寫鎖的三種狀態:
(1)當讀寫鎖是寫加鎖狀態時,在這個鎖被解鎖之前,所有試圖對這個鎖加鎖的線程都會被阻塞
(2)當讀寫鎖在讀加鎖狀態時,所有試圖以讀模式對它進行加鎖的線程都可以得到訪問權,但是以寫入模式對它進行加鎖的線程將會被阻塞
(3)當讀寫鎖在讀模式的鎖狀態時,如果有另外的線程試圖以寫入模式加鎖,讀寫鎖通常會阻塞隨後的讀模式鎖的請求,這樣可以避免讀模式鎖長期佔用,而等待的寫入模式鎖請求則長期阻塞。
讀寫鎖最適用於對資料結構的讀操作次數多於寫操作的場合,因為,讀模式鎖定時可以共用,而寫入模式鎖定時只能某個線程獨佔資源,因而,讀寫鎖也可以叫做個共用-獨佔鎖。
處理讀者-寫者問題的兩種常見策略是強讀者同步(strong reader synchronization)和強寫者同步(strong writer synchronization). 在強讀者同步中,總是給讀者更高的優先權,只要寫者當前沒有進行寫操作,讀者就可以獲得存取權限;而在強寫者同步中,則往往將優先權交付給寫者,而讀者只能等到所有正在等待的或者是正在執行的寫者結束以後才能執行。關於讀者-寫者模型中,由於讀者往往會要求查看最新的資訊記錄,所以航班訂票系統往往會使用強寫者同步策略,而圖書館查閱系統則採用強讀者同步策略。
讀寫鎖機制是由posix提供的,如果寫者沒有持有讀寫鎖,那麼所有的讀者多可以持有這把鎖,而一旦有某個寫者阻塞在上鎖的時候,那麼就由posix系統來決定是否允許讀者擷取該鎖。
2.讀寫鎖相關的API2.1 初始化和銷毀讀寫鎖
對於讀寫鎖變數的初始化可以有兩種方式,一種是通過給一個靜態分配的讀寫鎖賦予常值PTHREAD_RWLOCK_INITIALIZER來初始化它,另一種方法就是通過調用pthread_rwlock_init()來動態初始化。而當某個線程不再需要讀寫鎖的時候,可以通過調用pthread_rwlock_destroy來銷毀該鎖。函數原型如下:
1 #include 2 int pthread_rwlock_init(pthread_rwlock_t *rwptr, const pthread_rwlockattr_t *attr);3 int pthread_rwlock_destroy(pthread_rwlock_t *rwptr);
這兩個函數如果執行成功均返回0,如果出錯則返回錯誤碼。
在釋放某個讀寫鎖佔用的記憶體之前,要先通過pthread_rwlock_destroy對讀寫鎖進行清理,釋放由pthread_rwlock_init所分配的資源。在初始化某個讀寫鎖的時候,如果屬性指標attr是個null 指標的話,表示預設的屬性;如果想要使用非預設屬性,則要使用到下面的兩個函數:
1 #include 2 int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);3 int pthread_rwlockattr_destroy(pthread_rwlockatttr_t *attr);
這兩個函數同樣的,如果執行成功返回0,失敗返回錯誤碼。
這裡還需要說明的是,當初始化讀寫鎖完畢以後呢,該鎖就處於一個非鎖定狀態。資料類型為pthread_rwlockattr_t的某個屬性對象一旦初始化了,就可以通過不同的函數調用來啟用或者是禁用某個特定的屬性。
2.2 擷取和釋放讀寫鎖
讀寫鎖的資料類型是pthread_rwlock_t,如果這個資料類型中的某個變數是靜態分配的,那麼可以通過給它賦予常值PTHREAD_RWLOCK_INITIALIZAR來初始化它。pthread_rwlock_rdlock()用來擷取讀出鎖,如果相應的讀出鎖已經被某個寫入者佔有,那麼就阻塞調用線程。pthread_rwlock_wrlock()用來擷取一個寫入鎖,如果相應的寫入鎖已經被其它寫入者或者一個或多個讀出者佔有,那麼就阻塞該調用線程;pthread_rwlock_unlock()用來釋放一個讀出或者寫入鎖。函數原型如下:
1 #include 2 int pthread_rwlock_rdlock(pthread_rwlock_t *rwptr);3 int pthread_rwlock_wrlock(pthread_rwlock_t *rwptr);4 int pthread_rwlock_unlock(pthread_rwlock_t *rwptr);
這三個函數若調用成功則返回0,失敗就返回錯誤碼。要注意的是其中擷取鎖的兩個函數的操作都是阻塞操作,也就是說擷取不到鎖的話,那麼調用線程不是立即返回,而是阻塞執行。有寫情況下,這種阻塞式的擷取所得方式可能不是很適用,所以,接下來引入兩個採用非阻塞方式擷取讀寫鎖的函數pthread_rwlock_tryrdlock()和pthread_rwlock_trywrlock(),非阻塞方式下擷取鎖的時候,如果不能馬上擷取到,就會立即返回一個EBUSY錯誤,而不是把調用線程投入到睡眠等待。函數原型如下:
1 #include 2 int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwptr);3 int pthread_rwlock_trywrlock(pthread_rwlock_t *rwptr);
同樣地,這兩個函數調用成功返回0,失敗返回錯誤碼。
3.例子
https://github.com/helianthuslulu/LINUX_IPC
讀寫鎖詳解