無鎖編程:lock-free原理

定義

無鎖編程是指在不使用鎖的情況下,在多線程環境下實現多變量的同步。即在沒有線程阻塞的情況下實現同步。這樣可以避免競態、死鎖等問題。

原理

CAS是指Compare-and-swap或Compare-and-Set 
CAS是一個原子操作,用於多線程環境下的同步。它比較內存中的內容和給定的值,只有當兩者相同時(說明其未被修改),纔會修改內存中的內容。 
實現如下:

<code class="hljs perl has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> compare_and_swap(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span>* reg, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> oldval, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> newval)
{
  ATOMIC();
  <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> old_reg_val = <span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">*reg</span>;
  <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (old_reg_val == oldval)
     <span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">*reg</span> = newval;
  END_ATOMIC();
  <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> old_reg_val;
}</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li></ul>

<code class="hljs perl has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">bool compare_and_swap(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> <span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">*accum</span>, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> <span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">*dest</span>, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> newval)
{
  <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (<span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">*accum</span> == <span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">*dest</span>) {
      <span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">*dest</span> = newval;
      <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> true;
  } <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">else</span> {
      <span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">*accum</span> = <span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">*dest</span>;
      <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> false;
  }
}</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li></ul>

返回bool值得好處是可以知道是否設置成功。

在實際環境中,使用的是:

<code class="hljs r has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">bool __sync_bool_compare_and_swap (type *ptr, type oldval, type newval, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">...</span>)
type __sync_val_compare_and_swap (type *ptr, type oldval, type newval, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">...</span>)</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li></ul>

在使用CAS時,需要先獲取操作變量的值並放到oldval中,之後調用cas函數,直到調用成功。例如給變量val賦值

<code class="hljs scala has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">while</span>(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">true</span>)
{
    int oldval=<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">val</span>;
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span>(__sync_bool_compare_and_swap(&<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">val</span>, oldval, newval))
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">break</span>;
}</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li></ul>

ABA問題

在多線程環境中,使用lock-free的CAS時,如果一個線程對變量修改2次,第2次修改後的值和第1次修改前的值相同,那麼可能就會出現ABA問題。以上面的例子爲例: 
假設有兩個線程P1和P2,P1執行完int oldval=val後被其他線程搶佔。P2線程在此期間修改了val的值(可能多次修改),但最終val的值和修改前一樣。當P1線程之後運行CAS函數時,並不能發現這個問題。這就是ABA問題。

解決方法

一個常用的方法是添加額外的“tag”或“stamp”位來標記是指針是否被修改過。

參考: 
Compare-and-swap

ABA problem

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章