1. 程式人生 > >linux自旋鎖——讀寫鎖

linux自旋鎖——讀寫鎖

在一些程式中存在讀者寫者問題,也就是說,對某些資源的訪問會 存在兩種可能的情況,一種是訪問必須是排它性的,就是獨佔的意思,這稱作寫操作;另一種情況就是訪問方式可以是共享的,就是說可以有多個執行緒同時去訪問某個資源,這種就稱作讀操作。這個問題模型是從對檔案的讀寫操作中引申出來的。

一次只有一個執行緒可以佔有寫模式的讀寫鎖, 但是可以有多個執行緒同時佔有讀模式的讀寫鎖. 正是因為這個特性,當讀寫鎖是寫加鎖狀態時, 在這個鎖被解鎖之前, 所有試圖對這個鎖加鎖的執行緒都會被阻塞.當讀寫鎖在讀加鎖狀態時, 所有試圖以讀模式對它進行加鎖的執行緒都可以得到訪問權, 但是如果執行緒希望以寫模式對此鎖進行加鎖, 它必須直到所有的執行緒釋放鎖.

通常, 當讀寫鎖處於讀模式鎖住狀態時, 如果有另外執行緒試圖以寫模式加鎖, 讀寫鎖通常會阻塞隨後的讀模式鎖請求, 這樣可以避免讀模式鎖長期佔用, 而等待的寫模式鎖請求長期阻塞.
讀寫鎖適合於對資料結構的讀次數比寫次數多得多的情況. 因為, 讀模式鎖定時可以共享, 以寫模式鎖住時意味著獨佔, 所以讀寫鎖又叫共享-獨佔鎖.


讀寫鎖實際是一種特殊的自旋鎖,它把對共享資源的訪問者劃分成讀者和寫者,讀者只對共享資源進行讀訪問,寫者則需要對共享資源進行寫操作。這種鎖相對於自旋鎖而言,能提高併發性,因為在多處理器系統中,它允許同時有多個讀者來訪問共享資源,最大可能的讀者數為實際的邏輯CPU數。寫者是排他性的,一個讀寫鎖同時只能有一個寫者或多個讀者(與CPU數相關),但不能同時既有讀者又有寫者。

在讀寫鎖保持期間也是搶佔失效的。

 如果讀寫鎖當前沒有讀者,也沒有寫者,那麼寫者可以立刻獲得讀寫鎖,否則它必須自旋在那裡,直到沒有任何寫者或讀者。如果讀寫鎖沒有寫者,那麼讀者可以立即獲得該讀寫鎖,否則讀者必須自旋在那裡,直到寫者釋放該讀寫鎖。

讀寫鎖的三種狀態:
1.當讀寫鎖是寫加鎖狀態時,在這個鎖被解鎖之前,所有試圖對這個鎖加鎖的執行緒都會被阻塞
2.當讀寫鎖在讀加鎖狀態時,所有試圖以讀模式對它進行加鎖的執行緒都可以得到訪問權,但是以寫模式對它進行加鎖的執行緒將會被阻塞
3.當讀寫鎖在讀模式的鎖狀態時,如果有另外的執行緒試圖以寫模式加鎖,讀寫鎖通常會阻塞隨後的讀模式鎖的請求,這樣可以避免讀模式鎖長期佔用,而等待的寫模式鎖請求則長期阻塞。

處理讀者-寫者問題的兩種常見策略是強讀者同步(strong reader synchronization)和強寫者同步(strong writer synchronization).    
在強讀者同步中,總是給讀者更高的優先權,只要寫者當前沒有進行寫操作,讀者就可以獲得訪問許可權;而在強寫者同步中,則往往將優先權交付給寫者,而讀者只能等到所有正在等待的或者是正在執行的寫者結束以後才能執行。關於讀者-寫者模型中,由於讀者往往會要求檢視最新的資訊記錄,所以航班訂票系統往往會使用強寫者同步策略,而圖書館查閱系統則採用強讀者同步策略。
讀寫鎖機制是由posix提供的,如果寫者沒有持有讀寫鎖,那麼所有的讀者多可以持有這把鎖,而一旦有某個寫者阻塞在上鎖的時候,那麼就由posix系統來決定是否允許讀者獲取該鎖。

讀寫鎖相關的函式

(1).初始化和銷燬讀寫鎖
對於讀寫鎖變數的初始化可以有兩種方式,一種是通過給一個靜態分配的讀寫鎖賦予常值PTHREAD_RWLOCK_INITIALIZER來初始化它,另一種方法就是通過呼叫pthread_rwlock_init()來動態的初始化。
而當某個執行緒不再需要讀寫鎖的時候,可以通過呼叫pthread_rwlock_destroy來銷燬該鎖。函式原型如下:

#include <pthread.h>

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 <pthread.h>

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 <pthread.h>
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 <pthread.h>
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwptr);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwptr);

同樣地,這兩個函式呼叫成功返回0,失敗返回錯誤碼。
下面的程式碼實現了使用者的讀寫:
這裡寫圖片描述

這裡寫圖片描述

這裡寫圖片描述

這裡寫圖片描述

原始碼如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>


pthread_rwlock_t rwlock;
int global_num = 10;

void err_exit(const char *err_msg)
{ 
    printf("error:%s\n", err_msg);
    exit(1);
}

void* myread(void* arg)
{ 
    char *pthr_name = (char *)arg;
    while (1)
    { 
        /* 讀加鎖 */
        pthread_rwlock_rdlock(&rwlock);
        printf("執行緒%s進入臨界區,global_num = %d\n", pthr_name, global_num);
        sleep(1);
        printf("執行緒%s離開臨界區...\n", pthr_name);

        /* 讀解鎖 */
        pthread_rwlock_unlock(&rwlock);
        sleep(1);
    }
    return NULL;
}

void* mywrite(void* arg)
{ 
    char *pthr_name = (char *)arg;
    while (1)
    { 
        /* 寫加鎖 */
        pthread_rwlock_wrlock(&rwlock);
        /* 寫操作 */
        global_num++;

        printf("執行緒%s進入臨界區,global_num = %d\n", pthr_name, global_num);
        sleep(1);
        printf("執行緒%s離開臨界區...\n", pthr_name);

        /* 寫解鎖 */
        pthread_rwlock_unlock(&rwlock);
        sleep(2);
    }
    return NULL;
}

int main()
{ 
    pthread_t tid_read_1, tid_read_2, tid_write_1, tid_write_2;
    /* 建立4個執行緒,2個讀,2個寫 */
    /* 建立4個執行緒,2個讀,2個寫 */

    if (pthread_create(&tid_read_1, NULL, myread, "read_1") != 0)
        err_exit("create tid_read_1");

    if (pthread_create(&tid_read_2, NULL, myread, "read_1") != 0)
        err_exit("create tid_read_2");

    if (pthread_create(&tid_write_1, NULL, mywrite, "write_1") != 0)
        err_exit("create tid_write_1");

    if (pthread_create(&tid_write_2, NULL, mywrite, "write_2") != 0)
        err_exit("create tid_write_2");

    /* 隨便等待一個執行緒,防止main結束 */
    if (pthread_join(tid_read_1, NULL) != 0)
        err_exit("pthread_join()");

    return 0;
}

Makefile的編寫:

rwlock:rwlock.c
    gcc -o [email protected] $^ -lpthread

.PHONY:clean
clean:
    rm -f rwlock

執行結果:
這裡寫圖片描述

相關推薦

linux——

在一些程式中存在讀者寫者問題,也就是說,對某些資源的訪問會 存在兩種可能的情況,一種是訪問必須是排它性的,就是獨佔的意思,這稱作寫操作;另一種情況就是訪問方式可以是共享的,就是說可以有多個執行緒同時去訪問某個資源,這種就稱作讀操作。這個問題模型是從對檔案的讀寫操

互斥 的區別

轉自:https://blog.csdn.net/swl1993831/article/details/77948930   【互斥鎖】:共享資源的使用是互斥的,即一個執行緒獲得資源的使用權後就會將該資源加鎖,使用完後會將其解鎖,如果在使用過程中有其他執行緒想要獲取該資源的鎖,那麼

互斥 遞迴

互斥鎖(mutexlock): 最常使用於執行緒同步的鎖;標記用來保證在任一時刻,只能有一個執行緒訪問該物件,同一執行緒多次加鎖操作會造成死鎖;臨界區和互斥量都可用來實現此鎖,通常情況下鎖操作失敗會將該執行緒睡眠等待鎖釋放時被喚醒 自旋鎖(spinlock): 同樣

Linux下pthread的的優先順序問題

有這麼一個情況:有一個C實現的HashMap,需要在多個執行緒之間共享。對它的讀操作遠遠大於寫操作。所以採用了pthread的讀寫鎖來保障併發讀寫時的一致性。 現在測試發現的問題是:因為讀操作太多,導致寫操作一直拿不到鎖。按理說不應該啊,假如有三個執行緒,執行緒1 先申

Linux核心同步方法——

讀 - 寫自旋鎖    一個或多個任務可以併發地持有讀者鎖;相反,用於寫的鎖最多隻能被一個寫任務持有,而且此時不能有併發地讀操作。   讀/寫鎖也叫做共享/排斥鎖,或者併發/排斥鎖,因為這種鎖對讀者而言是共享地,對寫者以排斥形式獲取地。基本資料結構    在核心程式碼中,讀-

boost庫 學習筆記一 boost::share_mutex 互斥

#include <boost/thread/mutex> typedef boost::shared_mutex  rwMutex; typedef boost::shared_lock<rwMutex>  ReadLocker; typed

同步 互斥 區別

相交程序之間的關係主要有兩種,同步與互斥。所謂互斥,是指散步在不同程序之間的若干程式片斷,當某個程序執行其中一個程式片段時,其它程序就不能執行它 們之中的任一程式片段,只能等到該程序執行完這個程式片段後才可以執行。所謂同步,是指散步在不同程序之間的若干程式片斷,它們的執行必

:可重入 可中斷 公平

轉自:http://www.cnblogs.com/wihainan/p/4762800.html 侵刪 1.可重入鎖 如果鎖具備可重入性,則稱作為可重入鎖。 像synchronized和ReentrantLock都是可重入鎖,可重入性在我看來實際上表明瞭鎖的分配機

程序間通訊學習筆記-互斥 &&

第七章 互斥鎖和條件變數 互斥鎖是用於保護臨界區的,實際上是保護在臨界區中被操縱的資料,保護多個執行緒或者多個程序的共享資料。 #include<pthread.h> int pthread_mutex_lock(pthread_mutex_

Linux 學習筆記—執行緒同步之、屏障

3.2.1 讀寫鎖 讀寫鎖和互斥體類似,不過讀寫鎖有更高的並行性,互斥體要麼是鎖住狀態,要麼是不加鎖狀態,而且一次只有一個執行緒可以對其加鎖。而讀寫鎖可以有3個狀態,讀模式下鎖住狀態,寫模式下鎖住狀態,不加鎖狀態。一次只有一個執行緒可以佔有寫模式的讀寫鎖,但是多

和順序的實現原理

並且 保護 表達 min 返回 create creat rwlock ini 常用的同步原語鎖,到多核處理器時代鎖已經是必不可少的同步方式之一了。無論設計多優秀的多線程數據結構,都避不開有競爭的臨界區,此時高效的鎖顯得至關重要。鎖的顆粒度是框架/程序設計者所關註的,

adr 一個 失敗 基本 獲得 圖書館 不能 color pre 概述:   在一些程序中存在讀者寫者問題,也就是說,對某些資源的訪問會 存在兩種可能的情況,一種是訪問必須是排它行的,就是獨占的意思,這稱作寫操作; 另一種情況就是訪問方式可以是共享的,就是說可以有多個線程

018.多執行緒-悲觀、樂觀、重入、CAS無機制

悲觀鎖(Pessimistic Lock) 顧名思義,就是很悲觀。每次去拿資料的時候都認為別人會修改,所以都會上鎖。這樣別人想拿這個資料就會阻塞(block)直到它拿到鎖。傳統的關係型資料庫裡面就用到了很多這種鎖機制。比如:行鎖,表鎖,讀鎖,寫鎖等,都是在做操作之前先上鎖。

--、阻塞、可重入、悲觀、樂觀、偏向所、輕量級、重量級膨脹、物件和類

參考:http://blog.csdn.net/a314773862/article/details/54095819 自旋鎖 自旋鎖可以使執行緒在沒有取得鎖的時候,不被掛起,而轉去執行一個空迴圈,(即所謂的自旋,就是自己執行空迴圈),若在若干個空迴圈後,執行緒如果可以獲得

JAVA機制-可重入,可中斷,公平

部落格引用處(以下內容在原有部落格基礎上進行補充或更改,謝謝這些大牛的部落格指導): JAVA鎖機制-可重入鎖,可中斷鎖,公平鎖,讀寫鎖,自旋鎖 在併發程式設計中,經常遇到多個執行緒訪問同一個 共享資源 ,這時候作為開發者必須考慮如何維護資料一致性,在java中synchronized

執行緒同步機制(互斥量,,條件變數,屏障)

先知:      (1)執行緒是由程序建立而來,是cpu排程的最小單位。      (2)每個程序都有自己獨立的地址空間,而程序中的多個執行緒共用程序的資源,他們只有自己獨立的棧資源。 執行緒同步:      當多個控制執行緒共享相同的記憶體時,需要確保每個程序看到一致的

C# lock 語法糖實現原理--《.NET Core 底層入門》之,互斥,混合

在多執行緒環境中,多個執行緒可能會同時訪問同一個資源,為了避免訪問發生衝突,可以根據訪問的複雜程度採取不同的措施 原子操作適用於簡單的單個操作,無鎖演算法適用於相對簡單的一連串操作,而執行緒鎖適用於複雜的一連串操作 ### 原子操作 修改狀態要麼成功且狀態改變,要麼失敗且狀態不變,並且外部只能觀察到修改

面試官:你說說互斥、悲觀、樂觀的應用場景

前言 生活中用到的鎖,用途都比較簡單粗暴,上鎖基本是為了防止外人進來、電動車被偷等等。 但生活中也不是沒有 BUG 的,比如加鎖的電動車在「廣西 - 竊·格瓦拉」面前,鎖就是形同虛設,只要他願意,他就可以輕輕鬆鬆地把你電動車給「順走」,不然打工怎麼會是他這輩子不可能的事情呢?牛逼之人,必有牛

linux 和信號量【轉】

you 修改 變種 能夠 體系結構 當下 top 數據 啟用 轉自:http://blog.csdn.net/xu_guo/article/details/6072823 版權聲明:本文為博主原創文章,未經博主允許不得轉載。 自旋鎖最多只能被一個可執行線程

Linux:使用使線程同步

解釋 -s write des 返回值 沒有 enter red bubuko 基礎與控制原語 讀寫鎖 與互斥量類似,但讀寫鎖允許更高的並行性。其特性為:寫獨占,讀共享。 讀寫鎖狀態: 一把讀寫鎖具備三種狀態: 1. 讀模式下加鎖狀態 (讀鎖)