無鎖與等待無關程式設計

讀取-複製-更新(read-copy-update, RCU)與寬限期

/ AR-SEE-YOO /

假設幾乎每個讀取共享串列的人都只想看,而寫入很罕見。讓每個讀者都為一個鎖、甚至一次原子發布付出代價,未免可惜,畢竟他們的數量遠遠多過寫者。讀取-複製-更新(read-copy-update, RCU)是一種回收與同步技術,在 Linux 核心中大量使用,正是為這種「以讀為主」的情況而生。它了不起的承諾是:讀者幾乎免費——往往沒有鎖、沒有原子寫入、完全不必等待。

模式就藏在名字裡。要更新一個項目,寫者不會在讀者可能看到半改值的原地修改它。它改為做一份私有副本、修改該副本,然後原子地交換單一指標,使新讀者看到新版本,而那些已抓到舊版本指標的讀者則毫髮無傷地繼續使用它。舊版本接著被退役(retire)但尚未釋放。寫者等待一個寬限期(grace period):一段足夠長的時間,長到每個可能還握著舊版本指標的讀者都已完成並放手。唯有在寬限期之後,寫者才釋放舊副本。讀者用 rcu_read_lock 與 rcu_read_unlock 標出它的讀者端臨界區間(在經典核心 RCU 中這兩者基本上免費——它們只是停用先佔);寬限期的偵測,是靠等到每個 CPU 都通過了一個它不持有任何 RCU 參考的點,稱為靜止狀態(quiescent state)。

讀者幾乎免費的原因是核心所在:讀者從不寫入共享記憶體、也從不阻塞寫者,所以並行讀取能在多核心上近乎完美地擴展。成本落在寫者端——更新較重(複製、交換、等待寬限期),而寬限期在記憶體真正被回收前加上延遲,使退役記憶體滯留。因此 RCU 極適合讀取遠多於寫入的資料(路由表、設定、核心的目錄項快取),而不適合寫入繁重的結構。

/* 讀者:幾乎免費的臨界區間,沒有原子寫入。 */ rcu_read_lock(); p = rcu_dereference(shared); /* 讀取目前已發布的指標 */ use(p->field); rcu_read_unlock(); /* 寫者:複製、修改、發布、等一個寬限期,再釋放舊的。 */ q = copy_of(p); modify(q); rcu_assign_pointer(shared, q); /* 新讀者看到 q */ synchronize_rcu(); /* 等到所有舊讀者都離開 */ free(p); /* 現在安全 */

讀者無鎖地執行;寫者發布新版本,並在釋放那個沒人還能觸及的舊版本之前等待一個寬限期。

RCU 的讀者便宜,正是因為成本轉移到了寫者與延後回收;寬限期意味著待釋放的記憶體會滯留,所以 RCU 適合以讀為主的資料,不適合寫入繁重的結構。

又称
RCUgrace periodread-copy-update寬限期