當前位置:首頁 » 操作系統 » linux的讀寫鎖

linux的讀寫鎖

發布時間: 2023-06-12 00:06:09

linux下各種鎖的理解和使用及總結解決epoll驚群問題(面試常考)-

鎖出現的原因

臨界資源是什麼: 多線程執行流所共享的資源

鎖的作用是什麼, 可以做原子操作, 在多線程中針對臨界資源的互斥訪問... 保證一個時刻只有一個線程可以持有鎖對於臨界資源做修改操作...

任何一個線程如果需要修改,向臨界資源做寫入操作都必須持有鎖,沒有持有鎖就不能對於臨界資源做寫入操作.

鎖 : 保證同一時刻只能有一個線程對於臨界資源做寫入操作 (鎖地功能)

再一個直觀地代碼引出問題,再從指令集的角度去看問題

上述一個及其奇怪的結果,這個結果每一次運行都可能是不一樣的,Why ? 按照我們本來的想法是每一個線程 + 20000000 結果肯定應該是60000000呀,可以就是達不到這個值

為何? (深入匯編指令來看) 一定將過程放置到匯編指令上去看就可以理解這個過程了.

a++; 或者 a += 1; 這些操作的匯編操作是幾個步驟?

其實是三個步驟:

正常情況下,數據少,操作的線程少,問題倒是不大,想一想要是這樣的情況下,操作次數大,對齊操作的線程多,有些線程從中間切入進來了,在運算之後還沒寫回內存就另外一個線程切入進來同時對於之前的數據進行++ 再寫回內存, 啥效果,多次++ 操作之後結果確實一次加加操作後的結果。 這樣的操作 (術語叫做函數的重入) 我覺得其實就是重入到了匯編指令中間了,還沒將上一次運算的結果寫回內存就重新對這個內存讀取再運算寫入,結果肯定和正常的邏輯後的結果不一樣呀

來一幅圖片解釋一下

咋辦? 其實問題很清楚,我們只需要處理的是多條匯編指令不能讓它中間被插入其他的線程運算. (要想自己在執行匯編指令的時候別人不插入進來) 將多條匯編指令綁定成為一條指令不就OK了嘛。

也就是原子操作!!!

不會原子操作?操作系統給咱提供了線程的 綁定方式工具呀:mutex 互斥鎖(互斥量), 自旋鎖(spinlock), 讀寫鎖(readers-writer lock) 他們也稱作悲觀鎖. 作用都是一個樣,將多個匯編指令鎖成為一條原子操作 (此處的匯編指令也相當於如下的臨界資源)

悲觀鎖:鎖如其名,每次都悲觀地認為其他線程也會來修改數據,進行寫入操作,所以會在取數據前先加鎖保護,當其他線程想要訪問數據時,被阻塞掛起

樂觀鎖:每次取數據的時候,總是樂觀地認為數據不會被其他線程修改,因此不上鎖。但是在更新數據前, 會判斷其他數據在更新前有沒有對數據進行修改。

互斥鎖

最為常見使用地鎖就是互斥鎖, 也稱互斥量. mutex

特徵,當其他線程持有互斥鎖對臨界資源做寫入操作地時候,當前線程只能掛起等待,讓出CPU,存在線程間切換工作

解釋一下存在線程間切換工作 : 當線程試圖去獲取鎖對臨界資源做寫入操作時候,如果鎖被別的線程正在持有,該線程會保存上下文直接掛起,讓出CPU,等到鎖被釋放出來再進行線程間切換,從新持有CPU執行寫入操作

互斥鎖需要進行線程間切換,相比自旋鎖而言性能會差上許多,因為自旋鎖不會讓出CPU, 也就不需要進行線程間切換的步驟,具體原理下一點詳述

加互斥量(互斥鎖)確實可以達到要求,但是會發現運行時間非常的長,因為線程間不斷地切換也需要時間, 線程間切換的代價比較大.

相關視頻推薦

你繞不開的組件—鎖,4個方面手撕鎖的多種實現

「驚群」原理、鎖的設計方案及繞不開的「死鎖」問題

學習地址:C/C++Linux伺服器開發/後台架構師【零聲教育】-學習視頻教程-騰訊課堂

需要C/C++ Linux伺服器架構師學習資料加qun812855908獲取(資料包括 C/C++,Linux,golang技術,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,TCP/IP,協程,DPDK,ffmpeg 等),免費分享

自旋鎖

spinlock.自旋鎖.

對比互斥量(互斥鎖)而言,獲取自旋鎖不需要進行線程間切換,如果自旋鎖正在被別的線程佔用,該線程也不會放棄CPU進行掛起休眠,而是恰如其名的在哪裡不斷地循環地查看自旋鎖保持者(持有者)是否將自旋鎖資源釋放出來... (自旋地原來就是如此)

口語解釋自旋:持有自旋鎖的線程不釋放自旋鎖,那也沒有關系呀,我就在這里不斷地一遍又一遍地查詢自旋鎖是否釋放出來,一旦釋放出來我立馬就可以直接使用 (因為我並沒有掛起等待,不需要像互斥鎖還需要進行線程間切換,重新獲取CPU,保存恢復上下文等等操作)

哪正是因為上述這些特點,線程嘗試獲取自旋鎖,獲取不到不會採取休眠掛起地方式,而是原地自旋(一遍又一遍查詢自旋鎖是否可以獲取)效率是遠高於互斥鎖了. 那我們是不是所有情況都使用自旋鎖就行了呢,互斥鎖就可以放棄使用了嗎????

解釋自旋鎖地弊端:如果每一個線程都僅僅只是需要短時間獲取這個鎖,那我自旋占據CPU等待是沒啥問題地。要是線程需要長時間地使用占據(鎖)。。。 會造成過多地無端占據CPU資源,俗稱站著茅坑不拉屎... 但是要是僅僅是短時間地自旋,平衡CPU利用率 + 程序運行效率 (自旋鎖確實是在有些時候更加合適)

自旋鎖需要場景:內核可搶占或者SMP(多處理器)情況下才真正需求 (避免死鎖陷入死循環,瘋狂地自旋,比如遞歸獲取自旋鎖. 你獲取了還要獲取,但是又沒法釋放)

自旋鎖的使用函數其實和互斥鎖幾乎是一摸一樣地,僅僅只是需要將所有的mutex換成spin即可

僅僅只是在init存在些許不同

何為驚群,池塘一堆, 我瞄準一條插過去,但是好似所有的都像是覺著自己正在被插一樣的四處逃竄。 這個就是驚群的生活一點的理解

驚群現象其實一點也不少,比如說 accept pthread_cond_broadcast 還有多個線程共享epoll監視一個listenfd 然後此刻 listenfd 說來 SYN了,放在了SYN隊列中,然後完成了三次握手放在了 accept隊列中了, 現在問題是這個connect我應該交付給哪一個線程處理呢.

多個epoll監視准備工作的線程 就是這群 (),然後connet就是魚叉,這一叉下去肯定是所有的 epoll線程都會被驚醒 (多線程共享listenfd引發的epoll驚群)

同樣如果將上述的多個線程換成多個進程共享監視 同一個 listenfd 就是(多進程的epoll驚群現象)

咱再畫一個草圖再來理解一下這個驚群:

如果是多進程道理是一樣滴,僅僅只是將所有的線程換成進程就OK了

終是來到了今天的正題了: epoll驚群問題地解決上面了...

首先 先說說accept的驚群問題,沒想到吧accept 平時大家寫它的多線程地時候,多個線程同時accept同一個listensock地時候也是會存在驚群問題地,但是accept地驚群問題已經被Linux內核處理了: 當有新的連接進入到accept隊列的時候,內核喚醒且僅喚醒一個進程來處理

但是對於epoll的驚群問題,內核卻沒有直接進行處理。哪既然內核沒有直接幫我們處理,我們應該如何針對這種現象做出一定的措施呢?

驚群效應帶來的弊端: 驚群現象會造成epoll的偽喚醒,本來epoll是阻塞掛起等待著地,這個時候因為掛起等待是不會佔用CPU地。。。 但是一旦喚醒就會佔用CPU去處理發生地IO事件, 但是其實是一個偽喚醒,這個就是對於線程或者進程的無效調度。然而進程或者線程地調取是需要花費代價地,需要上下文切換。需要進行進程(線程)間的不斷切換... 本來多核CPU是用來支持高並發地,但是現在卻被用來無效地喚醒,對於多核CPU簡直就是一種浪費 (浪費系統資源) 還會影響系統的性能.

解決方式(一般是兩種)

Nginx的解決方式:

加鎖:驚群問題發生的前提是多個進程(線程)監聽同一個套接字(listensock)上的事件,所以我們只讓一個進程(線程)去處理監聽套接字就可以了。

畫兩張圖來理解一下:

上述還沒有進行一個每一個進程都對應一個listensock 而是多線程共享一個listensock 運行結果如下

所有的線程同時被喚醒了,但是實際上會處理連接的僅僅只是一個線程,

咱僅僅只是將主線程做如上這樣一個簡單的修改,每一個線程對應一個listensock;每一個線程一個獨有的監視窗口,將問題拋給內核去處理,讓內核去負載均衡 : 結果如下

僅僅喚醒一個線程來進行處理連接,解決了驚群問題

本文通過介紹兩種鎖入手,以及為什麼需要鎖,鎖本質就是為了保護,持有鎖你就有權力有能力操作寫入一定的臨界保護資源,沒有鎖你就不行需要等待,本質其實是將多條匯編指令綁定成原子操作

然後介紹了驚群現象,通過一個巧妙地例子,扔一顆石子,只是瞄準一條魚扔過去了,但是整池魚都被驚醒了,

對應我們地實際問題就是, 多個線程或者進程共同監視同一個listensock。。。。然後IO連接事件到來地時候本來僅僅只是需要一個線程醒過來處理即可,但是卻會使得所有地線程(進程)全部醒過來,造成不必要地進程線程間切換,多核CPU被浪費喔,系統資源被浪費

處理方式 一。 Nginx 源碼加互斥鎖處理。。 二。設置SO_REUSEPORT, 使得多個進程線程可以同時連接同一個port , 為每一個進程線程搞一個listensock... 將問題拋給內核去處理,讓他去負載均衡地僅僅將IO連接事件分配給一個進程或線程

㈡ linux 互斥鎖和讀寫鎖的區別與聯系

信號量與互斥鎖之間的區別:
1. 互斥量用於線程的互斥,信號量用於線程的同步。
這是互斥量和信號量的根本區別,也就是互斥和同步之間的區別。
互斥:是指某一資源同時只允許一個訪問者對其進行訪問,具有唯一性和排它性。但互斥無法限制訪問者對資源的訪問順序,即訪問是無序的。
同步:是指在互斥的基礎上(大多數情況),通過其它機制實現訪問者對資源的有序訪問。在大多數情況下,同步已經實現了互斥,特別是所有寫入資源的情況必定是互斥的。少數情況是指可以允許多個訪問者同時訪問資源
2. 互斥量值只能為0/1,信號量值可以為非負整數。
也就是說,一個互斥量只能用於一個資源的互斥訪問,它不能實現多個資源的多線程互斥問題。信號量可以實現多個同類資源的多線程互斥和同步。當信號量為單值信號量是,也可以完成一個資源的互斥訪問。
3. 互斥量的加鎖和解鎖必須由同一線程分別對應使用,信號量可以由一個線程釋放,另一個線程得到。

㈢ linux 多線程環境下的幾種鎖機制

NO1
互斥量(Mutex)
互斥量是實現最簡單的鎖類型,因此有一些教科書一般以互斥量為例對鎖原語進行描述。互斥量的釋放並不僅僅依賴於釋放操作,還可以引入一個定時器屬性。如果在釋放操作執行前發生定時器超時,則互斥量也會釋放代碼塊或共享存儲區供其他線程訪問。當有異常發生時,可使用try-finally語句來確保互斥量被釋放。定時器狀態或try-finally語句的使用可以避免產生死鎖。

遞歸鎖(Recursive
Lock)
遞歸鎖是指可以被當前持有該鎖的線程重復獲取,而不會導致該線程產生死鎖的鎖類型。對遞歸鎖而言,只有在當前持有線程的獲取鎖操作都有一個釋放操作與之對應時,其他線程才可以獲取該鎖。因此,在使用遞歸鎖時,必須要用足夠的釋放鎖操作來平衡獲取鎖操作,實現這一目標的最佳方式是在單入口單出口代碼塊的兩頭一一對應地使用獲取、釋放操作,做法和在普通鎖中一樣。遞歸鎖在遞歸函數中最有用。但是,總的來說,遞歸鎖比非遞歸鎖速度要慢。需要注意的是:調用線程獲得幾次遞歸鎖必須釋放幾次遞歸鎖。

以下為一個遞歸鎖的示例:

[cpp] view plain
Recursive_Lock L

void recursiveFunction (int count) {

L->acquire()

if (count > 0) {

count = count - 1;

recursiveFunction(count);

}

L->release();

}

讀寫鎖(Read-Write
lock) 讀寫鎖又稱為共享獨占鎖(shared-exclusive
lock)、多讀單寫鎖(multiple-read/single-write lock)或者非互斥信號量(non-mutual
exclusion
semaphore)。讀寫鎖允許多個線程同時進行讀訪問,但是在某一時刻卻最多隻能由一個線程執行寫操作。對於多個線程需要同時讀共享數據卻並不一定進行寫操作的應用來說,讀寫鎖是一種高效的同步機制。對於較長的共享數據,只為其設置一個讀寫鎖會導致較長的訪問時間,最好將其劃分為多個小段並設置多個讀寫鎖以進行同步。

這個讀寫鎖我們在學習資料庫的時候應該很熟悉的喲!

旋轉鎖(Spin
Lock)
旋轉鎖是一種非阻塞鎖,由某個線程獨占。採用旋轉鎖時,等待線程並不靜態地阻塞在同步點,而是必須「旋轉」,不斷嘗試直到最終獲得該鎖。旋轉鎖多用於多處理器系統中。這是因為,如果在單核處理器中採用旋轉鎖,當一個線程正在「旋轉」時,將沒有執行資源可供另一釋放鎖的線程使用。旋轉鎖適合於任何鎖持有時間少於將一個線程阻塞和喚醒所需時間的場合。線程式控制制的變更,包括線程上下文的切換和線程數據結構的更新,可能比旋轉鎖需要更多的指令周期。旋轉鎖的持有時間應該限制在線程上下文切換時間的50%到100%之間(Kleiman,1996年)。在線程調用其他子系統時,線程不應持有旋轉鎖。對旋轉鎖的不當使用可能會導致線程餓死,因此需謹慎使用這種鎖機制。旋轉鎖導致的餓死問題可使用排隊技術來解決,即每個等待線程按照先進先出的順序或者隊列結構在一個獨立的局部標識上進行旋轉。

學習了這些,果然受益匪淺,在今後的coding中,我得挨個試試咯。

㈣ Linux進程間通信(互斥鎖、條件變數、讀寫鎖、文件鎖、信號燈)

為了能夠有效的控制多個進程之間的溝通過程,保證溝通過程的有序和和諧,OS必須提供一定的同步機制保證進程之間不會自說自話而是有效的協同工作。比如在 共享內存的通信方式中,兩個或者多個進程都要對共享的內存進行數據寫入,那麼怎麼才能保證一個進程在寫入的過程中不被其它的進程打斷,保證數據的完整性 呢?又怎麼保證讀取進程在讀取數據的過程中數據不會變動,保證讀取出的數據是完整有效的呢?

常用的同步方式有: 互斥鎖、條件變數、讀寫鎖、記錄鎖(文件鎖)和信號燈.

互斥鎖:

顧名思義,鎖是用來鎖住某種東西的,鎖住之後只有有鑰匙的人才能對鎖住的東西擁有控制權(把鎖砸了,把東西偷走的小偷不在我們的討論范圍了)。所謂互斥, 從字面上理解就是互相排斥。因此互斥鎖從字面上理解就是一點進程擁有了這個鎖,它將排斥其它所有的進程訪問被鎖住的東西,其它的進程如果需要鎖就只能等待,等待擁有鎖的進程把鎖打開後才能繼續運行。 在實現中,鎖並不是與某個具體的變數進行關聯,它本身是一個獨立的對象。進(線)程在有需要的時候獲得此對象,用完不需要時就釋放掉。

互斥鎖的主要特點是互斥鎖的釋放必須由上鎖的進(線)程釋放,如果擁有鎖的進(線)程不釋放,那麼其它的進(線)程永遠也沒有機會獲得所需要的互斥鎖。

互斥鎖主要用於線程之間的同步。

條件變數:

上文中提到,對於互斥鎖而言,如果擁有鎖的進(線)程不釋放鎖,其它進(線)程永遠沒機會獲得鎖,也就永遠沒有機會繼續執行後續的邏輯。在實際環境下,一 個線程A需要改變一個共享變數X的值,為了保證在修改的過程中X不會被其它的線程修改,線程A必須首先獲得對X的鎖。現在假如A已經獲得鎖了,由於業務邏 輯的需要,只有當X的值小於0時,線程A才能執行後續的邏輯,於是線程A必須把互斥鎖釋放掉,然後繼續「忙等」。如下面的偽代碼所示:

1.// get x lock

2.while(x

㈤ linux怎麼把文件同時進行讀寫鎖

讀寫鎖與互斥量類似,不過讀寫鎖的並行性更高。
讀寫鎖可以有三種狀態:(1)讀模式加鎖;(2)寫模式加鎖;(3)不加鎖。
在寫加鎖狀態時,在解鎖之前,所有試圖對這個鎖加鎖的線程都會被阻塞。在讀加鎖狀態時,所有試圖以讀模式對它進行加鎖的線程都可以得到訪問許可權。但是如果線程希望以寫模式加鎖,它必須阻塞,直至所有的線程釋放讀鎖。
讀寫鎖很適合於對數據結構讀的次數遠大於寫的情況。

相關函數:
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock) // 成功則返回0,失敗則返回錯誤代碼
int pthread_rwlock_rdlock(pthread_rwlock_t *restrict rwlock) ;//讀模式加鎖
int pthread_rwlock_wrlock(pthread_rwlock_t *restrict rwlock);//寫模式加鎖
int pthread_rwlock_unlock(pthread_rwlock_t *restrick rwlock);
int pthread_rwlock_tryrdlock(pthread_rwlock_t *restrict rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *restrict rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *restrict rwlock);
相關示例:讀者寫者問題,這也是一個很經典的多線程題目,題目大意:有一個寫者多個讀者,多個讀者可以同時讀文件,但寫者在寫文件時不允許有讀者在讀取文件,同樣有讀者讀文件時
#include <stdio.h>
#include <pthread.h>

#define Read_Num 2

pthread_rwlock_t lock;

class Data
{
public:
Data(int i, float f): I(i),F(f)
{}
private:
int I;
float F;

};

Data *pdata = NULL;

void *read(void * arg)
{
int id = (int)arg;
while(true)
{

pthread_rwlock_rdlock(&lock);
printf(" reader %d is reading data!\n", id);
if(data == NULL)
{
printf("data is NULL\n");
}
else
{
printf("data: I = %d, F = %f \n", pdata->I, pdata->F);
}
pthread_rwlock_unlock(&lock);
}

pthread_exit(0);

}

void *write()
{
while(true)
{
pthread_rwlock_wrlock(&lock);
printf(" writer is writind data!\n");
if(pdata == NULL)
{
pdata = new Data(1, 1.1);
printf("Writer is writing data: %d, %f\n", pdata->I, pdata->F);
}
else
{
delete pdata;
pdata = NULL;
printf("writer free the data!");
}

pthread_rwlock_unlock(&lock);
}
pthread_exit(0);
}

void main()
{
pthread_t reader[Read_Num];
pthread_t writer;

for(int i = 0;i<Read_Num;i++)
{
pthread_create(&read[i],NULL,read,(void *)i);
}

pthread_create(writer, NULL, write, NULL);

sleep(1);
return 0;
}

㈥ 一文搞懂 , Linux內核—— 同步管理(下)

上面講的自旋鎖,信號量和互斥鎖的實現,都是使用了原子操作指令。由於原子操作會 lock,當線程在多個 CPU 上爭搶進入臨界區的時候,都會操作那個在多個 CPU 之間共享的數據 lock。CPU 0 操作了 lock,為了數據的一致性,CPU 0 的操作會導致其他 CPU 的 L1 中的 lock 變成 invalid,在隨後的來自其他 CPU 對 lock 的訪問會導致 L1 cache miss(更准確的說是communication cache miss),必須從下一個 level 的 cache 中獲取。

這就會使緩存一致性變得很糟,導致性能下降。所以內核提供一種新的同步方式:RCU(讀-復制-更新)。

RCU 解決了什麼

RCU 是讀寫鎖的高性能版本,它的核心理念是讀者訪問的同時,寫者可以更新訪問對象的副本,但寫者需要等待所有讀者完成訪問之後,才能刪除老對象。讀者沒有任何同步開銷,而寫者的同步開銷則取決於使用的寫者間同步機制。

RCU 適用於需要頻繁的讀取數據,而相應修改數據並不多的情景,例如在文件系統中,經常需要查找定位目錄,而對目錄的修改相對來說並不多,這就是 RCU 發揮作用的最佳場景。

RCU 例子

RCU 常用的介面如下圖所示:

為了更好的理解,在剖析 RCU 之前先看一個例子:

#include<linux/kernel.h>#include<linux/mole.h>#include<linux/init.h>#include<linux/slab.h>#include<linux/spinlock.h>#include<linux/rcupdate.h>#include<linux/kthread.h>#include<linux/delay.h>structfoo{inta;structrcu_headrcu;};staticstructfoo*g_ptr;staticintmyrcu_reader_thread1(void*data)//讀者線程1{structfoo*p1=NULL;while(1){if(kthread_should_stop())break;msleep(20);rcu_read_lock();mdelay(200);p1=rcu_dereference(g_ptr);if(p1)printk("%s: read a=%d\n",__func__,p1->a);rcu_read_unlock();}return0;}staticintmyrcu_reader_thread2(void*data)//讀者線程2{structfoo*p2=NULL;while(1){if(kthread_should_stop())break;msleep(30);rcu_read_lock();mdelay(100);p2=rcu_dereference(g_ptr);if(p2)printk("%s: read a=%d\n",__func__,p2->a);rcu_read_unlock();}return0;}staticvoidmyrcu_del(structrcu_head*rh)//回收處理操作{structfoo*p=container_of(rh,structfoo,rcu);printk("%s: a=%d\n",__func__,p->a);kfree(p);}staticintmyrcu_writer_thread(void*p)//寫者線程{structfoo*old;structfoo*new_ptr;intvalue=(unsignedlong)p;while(1){if(kthread_should_stop())break;msleep(250);new_ptr=kmalloc(sizeof(structfoo),GFP_KERNEL);old=g_ptr;*new_ptr=*old;new_ptr->a=value;rcu_assign_pointer(g_ptr,new_ptr);call_rcu(&old->rcu,myrcu_del);printk("%s: write to new %d\n",__func__,value);value++;}return0;}staticstructtask_struct*reader_thread1;staticstructtask_struct*reader_thread2;staticstructtask_struct*writer_thread;staticint__initmy_test_init(void){intvalue=5;printk("figo: my mole init\n");g_ptr=kzalloc(sizeof(structfoo),GFP_KERNEL);reader_thread1=kthread_run(myrcu_reader_thread1,NULL,"rcu_reader1");reader_thread2=kthread_run(myrcu_reader_thread2,NULL,"rcu_reader2");writer_thread=kthread_run(myrcu_writer_thread,(void*)(unsignedlong)value,"rcu_writer");return0;}staticvoid__exitmy_test_exit(void){printk("goodbye\n");kthread_stop(reader_thread1);kthread_stop(reader_thread2);kthread_stop(writer_thread);if(g_ptr)kfree(g_ptr);}MODULE_LICENSE("GPL");mole_init(my_test_init);mole_exit(my_test_exit);

執行結果是:

myrcu_reader_thread2:reada=0myrcu_reader_thread1:reada=0myrcu_reader_thread2:reada=0myrcu_writer_thread:writetonew5myrcu_reader_thread2:reada=5myrcu_reader_thread1:reada=5myrcu_del:a=0

RCU 原理

可以用下面一張圖來總結,當寫線程 myrcu_writer_thread 寫完後,會更新到另外兩個讀線程 myrcu_reader_thread1 和 myrcu_reader_thread2。讀線程像是訂閱者,一旦寫線程對臨界區有更新,寫線程就像發布者一樣通知到訂閱者那裡,如下圖所示。

寫者在拷貝副本修改後進行 update 時,首先把舊的臨界資源數據移除(Removal);然後把舊的數據進行回收(Reclamation)。結合 API 實現就是,首先使用 rcu_assign_pointer 來移除舊的指針指向,指向更新後的臨界資源;然後使用 synchronize_rcu 或 call_rcu 來啟動 Reclaimer,對舊的臨界資源進行回收(其中 synchronize_rcu 表示同步等待回收,call_rcu 表示非同步回收)。

為了確保沒有讀者正在訪問要回收的臨界資源,Reclaimer 需要等待所有的讀者退出臨界區,這個等待的時間叫做寬限期(Grace Period)。

Grace Period

中間的黃色部分代表的就是 Grace Period,中文叫做寬限期,從 Removal 到 Reclamation,中間就隔了一個寬限期,只有當寬限期結束後,才會觸發回收的工作。寬限期的結束代表著 Reader 都已經退出了臨界區,因此回收工作也就是安全的操作了。

寬限期是否結束,與 CPU 的執行狀態檢測有關,也就是檢測靜止狀態 Quiescent Status。

Quiescent Status

Quiescent Status,用於描述 CPU 的執行狀態。當某個 CPU 正在訪問 RCU 保護的臨界區時,認為是活動的狀態,而當它離開了臨界區後,則認為它是靜止的狀態。當所有的 CPU 都至少經歷過一次 Quiescent Status 後,寬限期將結束並觸發回收工作。

因為 rcu_read_lock 和 rcu_read_unlock 分別是關閉搶占和打開搶占,如下所示:

staticinlinevoid__rcu_read_lock(void){preempt_disable();}

staticinlinevoid__rcu_read_unlock(void){preempt_enable();}

所以發生搶占,就說明不在 rcu_read_lock 和 rcu_read_unlock 之間,即已經完成訪問或者還未開始訪問。

Linux 同步方式的總結

資料免費領

學習直通車

㈦ linux rcu原理

RCU, Read-Copy-Update,是Linux內核中的一種同步機制。RCU常被描述為讀寫鎖的替代品,它的特點是讀者並不需要直接與寫者進行同步,讀者與寫者也能並發的執行。

來一張圖片來描述下大體的操作吧羨薯:

多個讀者可以並發訪問臨界資源,同時使用rcu_read_lock/rcu_read_unlock來標定臨界區;
寫者(updater)在更新臨界資源的時候,拷貝一份副本作為基礎進行修改,當所有讀者離開臨界區後,把指向舊臨界資源的指針指向更新後的副本,並對舊資源進行回收處理;
圖中只顯兄羨者示一個寫者,當存在多個寫者的時候,需要在寫者之派模間進行互斥處理。

熱點內容
java設置顏色 發布:2025-04-06 13:42:57 瀏覽:748
javajar內存 發布:2025-04-06 13:42:54 瀏覽:939
iisasp腳本語言 發布:2025-04-06 13:39:54 瀏覽:286
多版本編譯方法 發布:2025-04-06 13:32:22 瀏覽:817
永劫無間和賽博朋克哪個配置高 發布:2025-04-06 13:14:16 瀏覽:155
新建android虛擬機 發布:2025-04-06 12:37:42 瀏覽:830
安卓如何獲取光遇密碼 發布:2025-04-06 12:28:23 瀏覽:832
從電腦拷貝文件到伺服器 發布:2025-04-06 12:10:12 瀏覽:39
rc5演算法 發布:2025-04-06 12:09:35 瀏覽:988
兩個數推演算法 發布:2025-04-06 12:00:50 瀏覽:41