C++11:原子交换函数compare_exchange_weak和compare_exchange_strong 初探

C++11:原子交换函数compare_exchange_weak和compare_exchange_strong

C++11中引入了mutex和lock_guard。方便进行加锁解锁操作,但是如果想要性能更高的无锁实现,则可以使用原子操作 Atomic,我们可以利用它巧妙地实现无锁同步。
进行atomic学习前,需要明白一个概念:
CAS(Compare and Swap, 比较并替换)当值为预期值的时候,就将该值替换为预期的值。
此外,CAS也可以表述为:compareAndSet:比较并设置;compareAndExchange:比较并交换

这里使用 JAVA AtomicInteger 为例,来说明一下 CAS 。

AtomicInteger atomicInteger = new AtomicInteger(5);
System.out.println(atomicInteger.compareAndSet(5, 2019));//true
System.out.println(atomicInteger.compareAndSet(5, 2020));//false

AtomicInteger 的初始值为 5,进行第一次 CAS的时候,替换成功,将 atomicInteger 的值替换为 2019,再进行第二次 CAS的时候,期望值为 5,但是此时 atomicInteger 的值为 2019,因此 cas 失败,返回 false 。

CA S最大的一个缺点就是:CAS 有自旋锁,即线程一直在执行逻辑,也就是一直让CPU进行计算操作。

CAS(Compare and Swap)是个原子操作,保证了如果需要更新的地址没有被他人改动多,那么它可以安全的写入。而这也是我们对于某个数据或者数据结构加锁要保护的内容,保证读写的一致性,不出现dirty data。现在几乎所有的CPU指令都支持CAS的原子操作。

Atomic

C++11给我们带来的Atomic一系列原子操作类,它们提供的方法能保证具有原子性。这些方法是不可再分的,获取这些变量值时,永远需要先获取修改前和修改后的值,而不会获得修改值过程中的数值。
进行原子操作的类都禁用了拷贝构造函数,原因是:原子读与原子写是2个独立的原子操作,无法保证2个独立操作加在一起是仍然保证原子性。
atomic提供了常见且容易理解的方法:
atomic提供了常见且容易理解的方法:

  1. store:原子写操作
  2. load:原子读操作
  3. exchange:允许两个数值进行交换,并保证整个过程是原子的;
  4. compare_exchange_weak与compare_exchange_strong:符合CAS(compare and set)概念,参数会要求在这里传入期待的数值和新的数值。它们对比变量的值和期待的值是否一致,如果是,则替换为用户指定的一个新的数值。如果不是,则将变量的值和期待的值交换。

compare_exchange_strong:atomic库中的一个函数,入参是3个,expect,desire,memory order,意思是如果当前的变量this的值等于expect值,则将this值改为desire,并返回true,否则,返回false,不进行修改,即进行一个读的操作。通常用于例如线程B等待线程A执行完毕,或者执行到某个步骤。此时线程B可以进行while等待,线程A在执行到对应步骤,将对应的原子变量置为expect值即可。类似于“接力运动”。这里由于会进行读写操作,所以,memory order一般是acq rel,而A线程由于要保证都执行完毕,执行顺序没有关系,所以一般是Release的memory order。

compare_exchange_weak和compare_exchange_strong

C++11中CAS实现:

template< class T>
struct atomic<T*>
{
public:
bool compare_exchange_weak( T& expected, T desired,
                           std::memory_order success,
                           std::memory_order failure );
bool compare_exchange_weak( T& expected, T desired,
                           std::memory_order success,
                           std::memory_order failure ) volatile;
bool compare_exchange_weak( T& expected, T desired,
                           std::memory_order order =
                               std::memory_order_seq_cst );
bool compare_exchange_weak( T& expected, T desired,
                           std::memory_order order =
                               std::memory_order_seq_cst ) volatile;
bool compare_exchange_strong( T& expected, T desired,
                             std::memory_order success,
                             std::memory_order failure );
bool compare_exchange_strong( T& expected, T desired,
                             std::memory_order success,
                             std::memory_order failure ) volatile;    
bool compare_exchange_strong( T& expected, T desired,
                             std::memory_order order =
                                 std::memory_order_seq_cst );
bool compare_exchange_strong( T& expected, T desired,
                             std::memory_order order =
                                 std::memory_order_seq_cst ) volatile;
...
};

当前值与期望值(expect)相等时,修改当前值为设定值(desired),返回true
当前值与期望值(expect)不等时,将期望值(expect)修改为当前值,返回false

weak版和strong版的区别:
weak版本的CAS允许偶然出乎意料的返回(比如在字段值和期待值一样的时候却返回了false),不过在一些循环算法中,这是可以接受的。通常它比起strong有更高的性能。

实例:

在非并发条件下,要实现一个栈的Push操作,我们可能有如下操作:
新建一个节点
将该节点的next指针指向现有栈顶
更新栈顶

但是在并发条件下,上述无保护的操作明显可能出现问题。下面举一个例子:

  1. 原栈顶为A。(此时栈状态: A->P->Q->…,我们约定从左到右第一个值为栈顶,P->Q代表p.next = Q)
  2. 线程1准备将B压栈。线程1执行完步骤2后被强占。(新建节点B,并使 B.next = A,即B->A)
  3. 线程2得到cpu时间片并完成将C压栈的操作,即完成步骤1、2、3。此时栈状态(此时栈状态: C->A->…)
  4. 这时线程1重新获得cpu时间片,执行步骤3。导致栈状态变为(此时栈状态: B->A->…)

结果线程2的操作丢失,这显然不是我们想要的结果。

那么我们如何解决这个问题呢?

只要保证步骤3更新栈顶时候,栈顶是我们在步骤2中获得顶栈顶即可。因为如果有其它线程进行操作,栈顶必然改变。

我们可以利用CAS轻松解决这个问题:如果栈顶是我们步骤2中获取顶栈顶,则执行步骤3。否则,自旋(即重新执行步骤2)。

 template<typename T>
class lock_free_stack
 {
		private:
	    struct node
	    {
	      T data;
	      node* next;
	  
	     node(T const& data_): 
	      data(data_)
	     {}
	   };
	 
	   std::atomic<node*> head;
	 public:
	   void push(T const& data)
	   {
	     node* const new_node=new node(data); 
	     new_node->next=head.load();  //如果head更新了,这条语句要重来一遍
	     while(!head.compare_exchange_weak(new_node->next,new_node));
	   }
};

在push方法里,atomic_compare_exchange_weak如果失败,则证明有其他线程更新了栈顶,而这个时候被其他线程更新的新栈顶值会被更新到new_node->next中,因此循环可以再次尝试压栈,而不需要程序更新new_node->next。

  • 21
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: compare_exchange_strongC++11中的一个原子操作函数,用于比较并交换操作。它接受三个参数:一个指向要修改的值的指针,一个期望的值和一个新值。如果指针指向的值等于期望的值,则将指针指向的值修改为新值,并返回true;否则不修改值,返回false。这个操作是原子的,即在多线程环境下也能保证操作的正确性。 ### 回答2: compare_exchange_strongC++11引入的一个原子操作,用于比较并交换某个指定的变量。 compare_exchange_strong函数的语法如下: ```c++ bool compare_exchange_strong(T& expected, T desired, memory_order order = memory_order_seq_cst) volatile noexcept; ``` 该函数expected与当前的原子变量进行比较,如果它们相等,则用desired的值替换原子变量的值,并返回true,表示交换成功。如果expected与原子变量的值不相等,则将expected更新为当前原子变量的值,并返回false,表示交换失败。 compare_exchange_strong的第三个参数order表示内存顺序,它控制了操作的一致性和可见性。可以使用不同的memory_order参数来指定不同的内存顺序。 compare_exchange_strong函数原子操作,可以保证多线程环境下的操作正确性。它可以用于实现一些线程间同步的机制,比如在无锁数据结构中确保多线程之间共享的变量的一致性。 总之,compare_exchange_strong是一个非常有用的原子操作,用于比较并交换某个指定的原子变量。它可以在多线程环境下保证操作的正确性和一致性,是实现线程同步的重要工具之一。 ### 回答3: compare_exchange_strong是一个原子操作,用于比较和交换操作的原子性保证。这个函数接受两个参数:一个期望的值和一个新的值。首先,它将原子对象的当前值与期望值进行比较,如果相等,则将原子对象的值更新为新的值,并返回true;如果不相等,则不更新原子对象的值,并返回false。 compare_exchange_strong操作的原子性保证了多个线程同时调用这个函数时,只有一个线程的期望值与原子对象的当前值相等,从而只有这个线程能够成功更新原子对象的值。其他线程会返回false,并且可以根据返回值进行相应的后续操作。 这个函数在多线程编程中非常有用,可以用于实现一些同步操作,特别是在需要保证变量的一致性和避免竞态条件的情况下。根据返回值的不同,我们可以在不同的情况下采取相应的策略,例如重新尝试操作,等待其他线程完成操作等。 需要注意的是,compare_exchange_strong操作并不能保证解决所有的并发问题,因此在使用时需要结合其他的同步机制来确保程序的正确性。另外,由于compare_exchange_strong是一个原子操作,其性能相对较低,因此在性能敏感的场景中需要谨慎使用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值