基於佇列的鎖:mcs lock簡介
今天在Quora閒逛,看到一個對於MCS Lock的問答。答題的哥們深入淺出的寫了一大篇,感覺非常不錯,特地翻譯出來。
原文翻譯
要理解MCS Locks的本質,必須先知道其產生背景(Why),然後才是其運作原理。就像原論文提到的,我們先從spin-lock說起。spin-lock 是一種基於test-and-set操作的鎖機制。
function Lock(lock){
while(test_and_set(lock)==1);
}
function Unlock(lock){
lock = 0;
}
test_and_set是一個原子操作,讀取lock,檢視lock值,如果是0,設定其為1,返回0。如果是lock值為1, 直接返回1。這裡lock的值0和1分別表示無鎖和有鎖。由於test_and_set的原子性,不會同時有兩個程式/執行緒同時進入該方法, 整個方法無須擔心併發操作導致的資料不一致。
一切看來都很完美,但是,有兩個問題:(1) test_and_set操作必須有硬體配合完成,必須在各個硬體(記憶體,二級快取,暫存器)等等之間保持 資料一致性,通訊開銷很大。(2) 他不保證公平性,也就是不會保證等待程式/執行緒按照FIFO的順序獲得鎖,可能有比較倒黴的程式/執行緒等待很長時間 才能獲得鎖。
為了解決上面的問題,出現一種Ticket Lock的演算法,比較類似於Lamport's backery algorithm。就像在麵包店裡排隊買麵包一樣,每個人先付錢,拿 一張票,等待他手中的那張票被叫到。下面是虛擬碼
ticket_lock {
int now_serving;
int next_ticket;
};
function Lock(ticket_lock lock){
//get your ticket atomically
int my_ticket = fetch_and_increment(lock.next_ticket);
while(my_ticket != now_serving){};
}
function Unlock(ticket_lock lock){
lock.now_serving++;
}
這裡用到了一個原子操作fetch_and_increment(實際上lock.now_serving++也必須保證是原子),這樣保證兩個程式/執行緒無法得到同一個ticket。 那麼上面的演算法解決的是什麼問題呢?只呼叫一次原子操作!!!最原始的那個演算法可是不停的在呼叫。這樣系統在保持一致性上的消耗就小很多。 第二,可以按照先來先得(FIFO)的規則來獲得鎖。沒有插隊,一切都很公平。
但是,這還不夠好。想想多處理器的架構,每個程式/執行緒佔用的處理器都在讀同一個變數,也就是now_serving。為什麼這樣不好呢,從多個CPU快取的 一致性考慮,每一個處理器都在不停的讀取now_serving本身就有不少消耗。最後單個程式/執行緒處理器對now_serving++的操作不但要重新整理到本地快取中,而且 要與其他的CPU快取保持一致。為了達到這個目的,系統會對所有的快取進行一致性處理,讀取新值只能序列讀取,然後再做操作,整個讀取時間是與處理器個數 線性相關。
說到這裡,才會聊到mcs佇列鎖。使用mcs鎖的目的是,讓獲得鎖的時間從O(n)變為O(1)。每個處理器都會有一個本地變數不用與其他處理器同步。虛擬碼如下
mcs_node{
mcs_node next;
int is_locked;
}
mcs_lock{
mcs_node queue;
}
function Lock(mcs_lock,mcs_node my_node){
my_node.next = NULL;
mcs_node predecessor = fetch_and_store(lock.queue,my_node);
if(predecessor!=NULL){
my_node.is_locked = true;
predecessor.next = my_node;
while(my_node.is_locked){};
}
}
function Unlock(mcs_lock lock,mcs_node my_node){
if(my_node.next == NULL){
if(compare_and_swap(lock.queue,my_node,NULL){
return ;
}
else{
while(my_node.next == NULL){};
}
}
my_node.next.is_locked = false;
}
這次程式碼多了不少。但是隻要記住每一個處理器在佇列裡都代表一個node,就不難理解整個邏輯。當我們試圖獲得鎖時,先將自己加入佇列,然後看看有沒有其他 人(predecessor)在等待,如果有則等待自己的is_lock節點被修改成false。當我們解鎖時,如果有一個後繼的處理器在等待,則設定其is_locked=false,喚醒他。
在Lock方法裡,用fetch_and_store來將本地node加入佇列,該操作是個原子操作。如果發現前面有人在等待,則將本節點加入等待節點的next域中,等待前面的處理器喚醒本節點。 如果前面沒有人,那麼直接獲得該鎖。
在Unlock方法中,首先檢視是否有人排在自己後面。這裡要注意,即使暫時發現後面沒有人,也必須用原子操作compare_and_swap確認自己是最後的一個節點。如果不能確認 必須等待之後節點排(my_node.next == NULL)上來。最後設定my_node.next.is_locked = false喚醒等待者。
最後我們看一下前面的問題是否解決了。原子操作的次數已經減少到最少,大多數時候只需要本地讀寫my_node變數。
註釋
- 1.原文來自於 http://www.quora.com/How-does-an-MCS-lock-work.
- 2.論文來自於 http://www.cs.rochester.edu/u/scott/papers/1991_TOCS_synch.pdf.
- 3.一些虛擬碼 http://www.cs.rochester.edu/research/synchronization/pseudocode/ss.html
- 4.關於多處理器的架構下的共享變數訪問的機制可以看看Java memory model或者搜一下NUMA與SMP架構,這方面本人也不是特別懂,還請各位賜教。
4.熟悉Java concurrent包的同學可以自己實現一下上面幾個演算法,可用的類和方法有:
AtomicReferenceFieldUpdater.compareAndSet().對應compare_and_swap
AtomicReferenceFieldUpdater.getAndSet().對應fetch_and_store
AtomicInteger.getAndIncrement().對應fetch_and_increment
相關文章
- 基於Redis的簡易延時佇列Redis佇列
- 深入學習Lock鎖(1)——佇列同步器佇列
- 棧與佇列簡介佇列
- 看圖理解基於陣列的佇列陣列佇列
- jquery動畫佇列簡單介紹jQuery動畫佇列
- 無鎖佇列佇列
- 【資料結構基礎】佇列簡介(使用ES6)資料結構佇列
- C語言 簡單的佇列(陣列佇列)C語言佇列陣列
- 【JavaSE】Lock鎖,獨佔鎖ReentrantLock的AQS原始碼,如何管理同步佇列。acquire方法和release方法JavaReentrantLockAQS原始碼佇列UI
- 關於thinkphp連貫操作加鎖的詳細介紹lockPHP
- 基於promise的阻塞式佇列設計Promise佇列
- 認識無鎖佇列佇列
- 佇列、資源與鎖佇列
- 基於 swoole 下 非同步佇列 API非同步佇列API
- 基於 Redis 的方式實現非同步佇列Redis非同步佇列
- 基於Dynomite的分散式延遲佇列MIT分散式佇列
- Procrastinate:基於PostgreSQL的Python任務佇列ASTSQLPython佇列
- 基於迴圈佇列的BFS的原理及實現佇列
- laravel 佇列的簡單使用Laravel佇列
- [擴充套件] Hyperf-redis-lock 基於hyperf的分散式鎖的實現套件Redis分散式
- 實現無鎖的棧與佇列(4)佇列
- 實現無鎖的棧與佇列(3)佇列
- 實現無鎖的棧與佇列(1)佇列
- 實現無鎖的棧與佇列(2)佇列
- KMQ:基於Apache Kafka的可靠性訊息佇列MQApacheKafka佇列
- Java:基於LinkedList實現棧和佇列Java佇列
- 無鎖資料結構:佇列資料結構佇列
- 基於 Redis 實現簡單的分散式鎖Redis分散式
- 【JavaSE】Lock鎖和synchronized鎖的比較,lock鎖的特性,讀寫鎖的實現。Javasynchronized
- MYSQL中一個特殊的MDL LOCK死鎖案列MySql
- AQS學習(一)自旋鎖原理介紹(為什麼AQS底層使用自旋鎖佇列?)AQS佇列
- 基於Redis的分散式鎖的簡單實現Redis分散式
- RabbitMQ-簡單佇列MQ佇列
- 訊息佇列簡史佇列
- 棧,佇列,優先順序佇列簡單介面使用佇列
- 個推基於 Apache Pulsar 的優先順序佇列方案Apache佇列
- Delayer 基於 Redis 的延遲訊息佇列中介軟體Redis佇列
- 基於asyncio和redis的Python分散式任務佇列RedisPython分散式佇列