定义
无锁编程是指在不使用锁的情况下,在多线程环境下实现多变量的同步。即在没有线程阻塞的情况下实现同步。这样可以避免竞态、死锁等问题。
原理
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