色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

CAS如何實現各種無鎖的數據結構

科技綠洲 ? 來源:Linux開發架構之路 ? 作者:Linux開發架構之路 ? 2023-11-10 11:00 ? 次閱讀

關于CAS等原子操作

在開始說無鎖隊列之前,我們需要知道一個很重要的技術就是CAS操作——Compare & Set,或是 Compare & Swap,現在幾乎所有的CPU指令都支持CAS的原子操作,X86下對應的是 CMPXCHG 匯編指令。有了這個原子操作,我們就可以用其來實現各種無鎖(lock free)的數據結構。

這個操作用C語言來描述就是下面這個樣子:意思就是說,看一看內存*reg里的值是不是oldval,如果是的話,則對其賦值newval。

int compare_and_swap (int* reg, int oldval, int newval)
{
int old_reg_val = *reg;
if (old_reg_val == oldval) {
*reg = newval;
}
return old_reg_val;
}

我們可以看到,old_reg_val 總是返回,于是,我們可以在 compare_and_swap 操作之后對其進行測試,以查看它是否與 oldval相匹配,因為它可能有所不同,這意味著另一個并發線程已成功地競爭到 compare_and_swap 并成功將 reg 值從 oldval 更改為別的值了。

這個操作可以變種為返回bool值的形式(返回 bool值的好處在于,可以調用者知道有沒有更新成功):

bool compare_and_swap (int *addr, int oldval, int newval)
{
if ( *addr != oldval ) {
return false;
}
*addr = newval;
return true;
}

與CAS相似的還有下面的原子操作:

  • Fetch And Add,一般用來對變量做 +1 的原子操作
  • Test-and-set,寫值到某個內存位置并傳回其舊值。匯編指令BST
  • Test and Test-and-set,用來低低Test-and-Set的資源爭奪情況

注:在實際的C/C++程序中,CAS的各種實現版本如下:

1)GCC的CAS

GCC4.1+版本中支持CAS的原子操作

bool __sync_bool_compare_and_swap (type *ptr, type oldval type newval, ...)
type __sync_val_compare_and_swap (type *ptr, type oldval type newval, ...)

2)Windows的CAS

在Windows下,你可以使用下面的Windows API來完成CAS:

InterlockedCompareExchange ( __inout LONG volatile *Target,
__in LONG Exchange,
__in LONG Comperand);

3) C++11中的CAS

C++11中的STL中的atomic類的函數可以讓你跨平臺。

template< class T >
bool atomic_compare_exchange_weak( std::atomic* obj,
T* expected, T desired );
template< class T >
bool atomic_compare_exchange_weak( volatile std::atomic* obj,
T* expected, T desired );

無鎖隊列的鏈表實現

初始化一個隊列的代碼很簡,初始化一個dummy結點(注:在鏈表操作中,使用一個dummy結點,可以少掉很多邊界條件的判斷),如下所示:

InitQueue(Q)
{
node = new node()
node->next = NULL;
Q->head = Q->tail = node;
}

我們先來看一下進隊列用CAS實現的方式,基本上來說就是鏈表的兩步操作:

第一步,把tail指針的next指向要加入的結點。tail->next = p;

第二步,把tail指針移到隊尾。tail = p;

EnQueue(Q, data) //進隊列
{
//準備新加入的結點數據
n = new node();
n->value = data;
n->next = NULL;
do {
p = Q->tail; //取鏈表尾指針的快照
} while( CAS(p->next, NULL, n) != TRUE);
//while條件注釋:如果沒有把結點鏈在尾指針上,再試
CAS(Q->tail, p, n); //置尾結點 tail = n;
}

我們可以看到,程序中的那個 do-while 的 Retry-Loop 中的 CAS 操作:如果 p->next 是 NULL,那么,把新結點 n 加到隊尾。如果不成功,則重新再來一次!

就是說,很有可能我在準備在隊列尾加入結點時,別的線程已經加成功了,于是tail指針就變了,于是我的CAS返回了false,于是程序再試,直到試成功為止。這個很像我們的搶電話熱線的不停重播的情況。

但是你會看到,為什么我們的“置尾結點”的操作不判斷是否成功,因為:

  • 如果有一個線程T1,它的while中的CAS如果成功的話,那么其它所有的 隨后線程的CAS都會失敗,然后就會再循環,
  • 此時,如果T1 線程還沒有更新tail指針,其它的線程繼續失敗,因為tail->next不是NULL了。
  • 直到T1線程更新完 tail 指針,于是其它的線程中的某個線程就可以得到新的 tail 指針,繼續往下走了。
  • 所以,只要線程能從 while 循環中退出來,意味著,它已經“獨占”了,tail 指針必然可以被更新。
  • 這里有一個潛在的問題——如果T1線程在用CAS更新tail指針的之前,線程停掉或是掛掉了,那么其它線程就進入死循環了。下面是改良版的EnQueue()
EnQueue(Q, data) //進隊列改良版 v1
{
n = new node();
n->value = data;
n->next = NULL;
p = Q->tail;
oldp = p
do {
while (p->next != NULL)
p = p->next;
} while( CAS(p.next, NULL, n) != TRUE); //如果沒有把結點鏈在尾上,再試
CAS(Q->tail, oldp, n); //置尾結點
}

我們讓每個線程,自己fetch 指針 p 到鏈表尾。但是這樣的fetch會很影響性能。而且,如果一個線程不斷的EnQueue,會導致所有的其它線程都去 fetch 他們的 p 指針到隊尾,能不能不要所有的線程都干同一個事?這樣可以節省整體的時間?

比如:直接 fetch Q->tail 到隊尾?因為,所有的線程都共享著 Q->tail,所以,一旦有人動了它后,相當于其它的線程也跟著動了,于是,我們的代碼可以改進成如下的實現:

EnQueue(Q, data) //進隊列改良版 v2
{
n = new node();
n->value = data;
n->next = NULL;
while(TRUE) {
//先取一下尾指針和尾指針的next
tail = Q->tail;
next = tail->next;
//如果尾指針已經被移動了,則重新開始
if ( tail != Q->tail ) continue;
//如果尾指針的 next 不為NULL,則 fetch 全局尾指針到next
if ( next != NULL ) {
CAS(Q->tail, tail, next);
continue;
}
//如果加入結點成功,則退出
if ( CAS(tail->next, next, n) == TRUE ) break;
}
CAS(Q->tail, tail, n); //置尾結點
}

上述的代碼還是很清楚的,相信你一定能看懂,而且,這也是 Java 中的 ConcurrentLinkedQueue 的實現邏輯,當然,我上面的這個版本比 Java 的好一點,因為沒有 if 嵌套,嘿嘿。

好了,我們解決了EnQueue,我們再來看看DeQueue的代碼:(很簡單,我就不解釋了)

DeQueue(Q) //出隊列
{
do{
p = Q->head;
if (p->next == NULL){
return ERR_EMPTY_QUEUE;
}
while( CAS(Q->head, p, p->next) != TRUE );
return p->next->value;
}

我們可以看到,DeQueue的代碼操作的是 head->next,而不是 head 本身。這樣考慮是因為一個邊界條件,我們需要一個dummy的頭指針來解決鏈表中如果只有一個元素,head 和 tail 都指向同一個結點的問題,這樣 EnQueue 和 DeQueue 要互相排斥了。

但是,如果 head 和 tail 都指向同一個結點,這意味著隊列為空,應該返回 ERR_EMPTY_QUEUE,但是,在判斷 p->next == NULL 時,另外一個EnQueue操作做了一半,此時的 p->next 不為 NULL了,但是 tail 指針還差最后一步,沒有更新到新加的結點,這個時候就會出現,在 EnQueue 并沒有完成的時候, DeQueue 已經把新增加的結點給取走了,此時,隊列為空,但是,head 與 tail 并沒有指向同一個結點。如下所示:

圖片

雖然,EnQueue的函數會把 tail 指針置對,但是,這種情況可能還是會導致一些并發問題,所以,嚴謹來說,我們需要避免這種情況。于是,我們需要加入更多的判斷條件,還確保這個問題。下面是相關的改進代碼:

DeQueue(Q) //出隊列,改進版
{
while(TRUE) {
//取出頭指針,尾指針,和第一個元素的指針
head = Q->head;
tail = Q->tail;
next = head->next;
// Q->head 指針已移動,重新取 head指針
if ( head != Q->head ) continue;

// 如果是空隊列
if ( head == tail && next == NULL ) {
return ERR_EMPTY_QUEUE;
}

//如果 tail 指針落后了
if ( head == tail && next == NULL ) {
CAS(Q->tail, tail, next);
continue;
}
//移動 head 指針成功后,取出數據
if ( CAS( Q->head, head, next) == TRUE){
value = next->value;
break;
}
}
free(head); //釋放老的dummy結點
return value;
}

CAS的ABA問題

所謂ABA,問題基本是這個樣子:

  • 進程P1在共享變量中讀到值為A
  • P1被搶占了,進程P2執行
  • P2把共享變量里的值從A改成了B,再改回到A,此時被P1搶占。
  • P1回來看到共享變量里的值沒有被改變,于是繼續執行。

雖然P1以為變量值沒有改變,繼續執行了,但是這個會引發一些潛在的問題。ABA問題最容易發生在lock free 的算法中的,CAS首當其沖,因為CAS判斷的是指針的值。很明顯,值是很容易又變成原樣的。

比如上述的DeQueue()函數,因為我們要讓head和tail分開,所以我們引入了一個dummy指針給head,當我們做CAS的之前,如果head的那塊內存被回收并被重用了,而重用的內存又被EnQueue()進來了,這會有很大的問題。(內存管理中重用內存基本上是一種很常見的行為)

這個例子你可能沒有看懂,一個活生生的例子——

你拿著一個裝滿錢的手提箱在飛機場,此時過來了一個火辣性感的美女,然后她很暖昧地挑逗著你,并趁你不注意的時候,把用一個一模一樣的手提箱和你那裝滿錢的箱子調了個包,然后就離開了,你看到你的手提箱還在那,于是就提著手提箱去趕飛機去了。

這就是ABA的問題。

解決ABA的問題

維基百科上給了一個解——使用double-CAS(雙保險的CAS),例如,在32位系統上,我們要檢查64位的內容

  • 一次用CAS檢查雙倍長度的值,前半部是值,后半部分是一個計數器。
  • 只有這兩個都一樣,才算通過檢查,要吧賦新的值。并把計數器累加1。

這樣一來,ABA發生時,雖然值一樣,但是計數器就不一樣(但是在32位的系統上,這個計數器會溢出回來又從1開始的,這還是會有ABA的問題)

當然,我們這個隊列的問題就是不想讓那個內存重用,這樣明確的業務問題比較好解決。

SafeRead(q)
{
loop:
p = q->next;
if (p == NULL){
return p;
}
Fetch&Add(p->refcnt, 1);
if (p == q->next){
return p;
}else{
Release(p);
}
goto loop;
}

其中的 Fetch&Add和Release分是是加引用計數和減引用計數,都是原子操作,這樣就可以阻止內存被回收了。

用數組實現無鎖隊列

使用數組來實現隊列是很常見的方法,因為沒有內存的分部和釋放,一切都會變得簡單,實現的思路如下:

  • 數組隊列應該是一個ring buffer形式的數組(環形數組)
  • 數組的元素應該有三個可能的值:HEAD,TAIL,EMPTY(當然,還有實際的數據)
  • 數組一開始全部初始化成EMPTY,有兩個相鄰的元素要初始化成HEAD和TAIL,這代表空隊列。
  • EnQueue操作。假設數據x要入隊列,定位TAIL的位置,使用double-CAS方法把(TAIL, EMPTY) 更新成 (x, TAIL)。需要注意,如果找不到(TAIL, EMPTY),則說明隊列滿了。
  • DeQueue操作。定位HEAD的位置,把(HEAD, x)更新成(EMPTY, HEAD),并把x返回。同樣需要注意,如果x是TAIL,則說明隊列為空。

算法的一個關鍵是——如何定位HEAD或TAIL?

  • 我們可以聲明兩個計數器,一個用來計數EnQueue的次數,一個用來計數DeQueue的次數。
  • 這兩個計算器使用使用Fetch&ADD來進行原子累加,在EnQueue或DeQueue完成的時候累加就好了。
  • 累加后求個模什么的就可以知道TAIL和HEAD的位置了。

如下圖所示:

圖片

小結

以上基本上就是所有的無鎖隊列的技術細節,這些技術都可以用在其它的無鎖數據結構上。

  • 無鎖隊列主要是通過CAS、FAA這些原子操作,和Retry-Loop實現。
  • 對于Retry-Loop,我個人感覺其實和鎖什么什么兩樣。只是這種“鎖”的粒度變小了,主要是“鎖”HEAD和TAIL這兩個關鍵資源。而不是整個數據結構
聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 內存
    +關注

    關注

    8

    文章

    3028

    瀏覽量

    74100
  • 數據結構
    +關注

    關注

    3

    文章

    573

    瀏覽量

    40146
  • CAS
    CAS
    +關注

    關注

    0

    文章

    34

    瀏覽量

    15213
  • 線程
    +關注

    關注

    0

    文章

    505

    瀏覽量

    19700
收藏 人收藏

    評論

    相關推薦

    常見的數據結構

    順序表結構的底層實現借助的就是數組,因此對于初學者來說,可以把順序表完全等價為數組,但實則不是這樣。數據結構是研究數據存儲方式的一門學科,它囊括的都是
    發表于 05-10 07:58

    OpenHarmony——內核IPC機制數據結構解析

    數據結構--互斥互斥又稱互斥型信號量,是一種特殊的二值性信號量,用于實現對共享資源的獨占式處理。任意時刻互斥的狀態只有開鎖或閉鎖,當
    發表于 09-08 11:44

    數據結構教程,下載

    1. 數據結構的基本概念 2. 算法與數據結構3. C語言的數據類型及其算法描述要點4. 學習算法與數據結構的意義與方法
    發表于 05-14 17:22 ?0次下載
    <b class='flag-5'>數據結構</b>教程,下載

    GPIB命令的數據結構

    針對GPIB命令的結構,提出一種存儲GPIB命令的數據結構。根據GPIB命令的層次關系的特點,選擇數據結構中“樹”的概念來存儲GPIB命令結點;并考慮程序實現的效率問題以及管理維護
    發表于 02-10 16:20 ?70次下載

    GPIB命令的數據結構

    針對GPIB命令的結構,提出一種存儲GPIB命令的數據結構。根據GPIB命令的層次關系的特點,選擇數據結構中“樹”的概念來存儲GPIB命令結點;并考慮程序實現的效率問題以及管理維護
    發表于 01-04 10:13 ?0次下載

    數據結構與算法

    全國C語言考試公共基礎知識點——數據結構與算法,該資料包含了有關數據結構與算法的全部知識點。
    發表于 03-30 14:27 ?0次下載

    數據結構是什么_數據結構有什么用

    數據結構是計算機存儲、組織數據的方式。數據結構是指相互之間存在一種或多種特定關系的數據元素的集合。通常情況下,精心選擇的數據結構可以帶來更高
    發表于 11-17 14:45 ?1.6w次閱讀
    <b class='flag-5'>數據結構</b>是什么_<b class='flag-5'>數據結構</b>有什么用

    java數據結構學習

    數據結構是對計算機內存中的數據的一種安排,數據結構包括 數組, 鏈表, 棧, 二叉樹, 哈希表等,算法則對對這些結構中的數據進行
    發表于 11-29 09:46 ?787次閱讀

    什么是數據結構?為什么要學習數據結構數據結構的應用實例分析

    本文檔的主要內容詳細介紹的是什么是數據結構?為什么要學習數據結構數據結構的應用實例分析包括了:數據結構在串口通信當中的應用,數據結構在按鍵
    發表于 09-26 15:45 ?14次下載
    什么是<b class='flag-5'>數據結構</b>?為什么要學習<b class='flag-5'>數據結構</b>?<b class='flag-5'>數據結構</b>的應用實例分析

    數據結構各種算法實現資料的C++模板概述

    本文檔的主要內容詳細介紹的是數據結構各種算法實現資料的C++模板概述。
    發表于 12-20 16:35 ?6次下載

    利用CAS技術實現隊列

    【 導讀 】:本文 主要講解利用CAS技術實現隊列。 關于隊列的
    的頭像 發表于 01-11 10:52 ?2297次閱讀
    利用<b class='flag-5'>CAS</b>技術<b class='flag-5'>實現</b><b class='flag-5'>無</b><b class='flag-5'>鎖</b>隊列

    關于CAS等原子操作介紹 隊列的鏈表實現方法

    ,X86下對應的是 CMPXCHG 匯編指令。有了這個原子操作,我們就可以用其來實現各種(lock free)的數據結構
    的頭像 發表于 05-18 09:12 ?3439次閱讀
    關于<b class='flag-5'>CAS</b>等原子操作介紹 <b class='flag-5'>無</b><b class='flag-5'>鎖</b>隊列的鏈表<b class='flag-5'>實現</b>方法

    NetApp的數據結構是如何演變的

    統一數據跨分布式資源進行管理,以實現數據移動的一致性和控制,安全、可見性、保護和訪問。 本文定義了數據結構及其體系結構,討論了
    發表于 08-25 17:15 ?0次下載
    NetApp的<b class='flag-5'>數據結構</b>是如何演變的

    CAS如何實現各種數據結構

    ,可用于在多線程編程中實現不被打斷的數據交換操作,從而避免多線程同時改寫某?數據時由于執行順序不確定性以及中斷的不可預知性產?的數據不一致問題 有了
    的頭像 發表于 11-13 15:38 ?825次閱讀
    <b class='flag-5'>無</b><b class='flag-5'>鎖</b><b class='flag-5'>CAS</b>如何<b class='flag-5'>實現</b><b class='flag-5'>各種</b><b class='flag-5'>無</b><b class='flag-5'>鎖</b>的<b class='flag-5'>數據結構</b>

    redis數據結構的底層實現

    Redis是一種內存鍵值數據庫,常用于緩存、消息隊列、實時數據分析等場景。它的高性能得益于其精心設計的數據結構和底層實現。本文將詳細介紹Redis常用的
    的頭像 發表于 12-05 10:14 ?624次閱讀
    主站蜘蛛池模板: 多男同时插一个女人8p| 99久热这里精品免费| 秋霞电影院午夜伦高清| 久久综合电影| 久久88综合| 好紧小嫩嫩水的10p| 国产午夜在线观看视频| 国产欧美精品一区二区色综合| 国产 亚洲 中文字幕 在线| 大肥婆丰满大肥奶bbw肥| 成人免费公开视频| 超碰在线 视频| 成人精品视频| 国产成人久久精品激情| 国产99久久亚洲综合精品西瓜tv| 俄罗斯14一18处交| 国产成人综合在线观看网站| 国产精品AV视频一二三区| 国产精品v欧美精品v日韩| 国产乱人精品视频AV麻豆| 国产偷国产偷亚州清高| 黄页网址大全免费观看| 久久 这里只精品 免费| 久久成人免费观看草草影院| 久久人妻无码毛片A片麻豆| 麻生希快播在线| 欧美性暴力变态xxxx| 视频成人永久免费视频| 亚洲国产精品第一影院在线观看| 亚洲三级视频| 中文字幕亚洲欧美日韩2019| 99re久久超碰视频精品| 不知火舞vs精子| 国产精品人妻在线观看| 精品国产乱码久久久久久上海公司| 久久久久国产精品美女毛片| 伦理片天堂eeuss影院| 欧美性狂猛bbbbbbxxxx| 特大黑人娇小亚洲女mp4| 亚洲精品一二三| 99精品国产免费久久久久久下载 |