atomic和volatile的操作的原子性

       c++11提供了atomic来实现无锁并发数据结构,用起来很简单。

     

#include 
#include 
#include 
#include 

using namespace std;

//std::mutex mu_lock;
//int num = 0;
std::atomic num(0);

void testThread1()
{
    for (int i = 0;i < 1000;i++) {
//        lock_guard lock(mu_lock);
        num += 1;
    }
}

void testThread2()
{
    for (int i = 0;i < 1000;i++)
    {
//        lock_guard lock(mu_lock);
        num += 1;
    }
}

int main()
{
    std::thread thread1(testThread1);
    std::thread thread2(testThread2);

    thread1.join();
    thread2.join();
    std::cout << "num = " << num << std::endl;
}

     注释的部分是在没有原子操作支持情况下的并发设计(加锁),虽然可行但是在效率上没有atomic高。

     通常, 标准原子类型是不能拷贝和赋值,因赋值和拷贝调用了两个对象, 这就就破坏了操作的原子性。 在这样的情况下, 拷贝构造和拷贝赋值都会将第一个对象的值进行读取, 然后再写入另外一个。 对于两个独立的对象, 这里就有两个独立的操作了, 合并这两个操作必定是不原子的。 因此, 操作就不被允许。 但是, 因为可以隐式转化成对应的内置类型, 所以这些类型依旧支持赋值(注意这里是c++内置类型,而非自定义类型)。   

    另一件需要注意的事情时, atmoic_bool类型的赋值操作符不同于通常的操作,通常的赋值操作符返回对应类型的引用, 再赋给对应的对象。 但是在原子类型中返回对应类型的值而非引用, 这是一种常见的模式:赋值操作通过返回值(返回相关的非原子类型)完成, 而非返回引用。 如果一个原子变量的引用被返回了, 任何依赖与这个赋值结果的代码都需要显式加载这个值, 潜在的问题是, 结果可能会被另外的线程所修改。 通过使用返回非原子值进行赋值的方式, 你可以避免这些多余的加载过程, 并且得到的值就是实际存储的值。

   atomic的底层采用了cas的实现原理,CAS操作需要输入两个数值,一个旧值(期望操作前的值)和一个新值,在操作期间先比较下在旧值有没有发生变化,如果没有发生变化,才交换成新值,发生了变化则不交换。更详细的介绍:点击打开链接

   虽然atomic能保证变量的线程安全性,但是也不是任何情况下都可以的,看下面的例子:

   

//
//  main.cpp
//  Demo
//
//  Created by 杜国超 on 17/7/9.
//  Copyright © 2017年 杜国超. All rights reserved.
//

#include 
#include 
#include 
#include 
#include 

using namespace std;

std::atomic num(0);
volatile num1 = 0;

void testThread1()
{
    for (int i = 0;i < 1000;i++) {
        int temp = num;
        /Do Something With temp,比如temp++/
        temp++;
        /Do Something End/
        num = temp;
        num1++; 
    }
}

void testThread2()
{
    for (int i = 0;i < 1000;i++)
    {
        int temp = num;
        /Do Something With temp,比如temp++/
        temp++;
        /Do Something End/
        num = temp;
        num1++; 
    }
}

int main()
{
    list temp;
    std::thread thread1(testThread1);
    std::thread thread2(testThread2);

    thread1.join();
    thread2.join();
    std::cout << "num = " << num << std::endl;
    std::cout << "num1 = " << num1 << std::endl;
}

   上面的例子,不论是num还是num1我们是得不到正确的结果的,因为atmoic操作只能保证num=temp,这一句的原子性,而注释中间的部分atomic是无能为力的,我们经过了一系列的操作最终直接把结果付给了它,这样操作和赋值成为了两个部分,必定不是原子的。换句话说只有atomic模版本身已有的操作是原子的,只有它已重载的运算是原子的,我们必须使用这些操作来写变量,否则就要通过锁来实现了:

template 
struct __atomic_base<_Tp, true>
    : public __atomic_base<_Tp, false>
{
    typedef __atomic_base<_Tp, false> __base;
    _LIBCPP_INLINE_VISIBILITY
    __atomic_base() _NOEXCEPT _LIBCPP_DEFAULT
    _LIBCPP_INLINE_VISIBILITY
    _LIBCPP_CONSTEXPR __atomic_base(_Tp __d) _NOEXCEPT : __base(__d) {}
    
    //成员函数
    _LIBCPP_INLINE_VISIBILITY
    _Tp fetch_add(_Tp __op, memory_order __m = memory_order_seq_cst) volatile _NOEXCEPT
        {return __c11_atomic_fetch_add(&this->__a_, __op, __m);}
    _LIBCPP_INLINE_VISIBILITY
    _Tp fetch_add(_Tp __op, memory_order __m = memory_order_seq_cst) _NOEXCEPT
        {return __c11_atomic_fetch_add(&this->__a_, __op, __m);}
    _LIBCPP_INLINE_VISIBILITY
    _Tp fetch_sub(_Tp __op, memory_order __m = memory_order_seq_cst) volatile _NOEXCEPT
        {return __c11_atomic_fetch_sub(&this->__a_, __op, __m);}
    _LIBCPP_INLINE_VISIBILITY
    _Tp fetch_sub(_Tp __op, memory_order __m = memory_order_seq_cst) _NOEXCEPT
        {return __c11_atomic_fetch_sub(&this->__a_, __op, __m);}
    _LIBCPP_INLINE_VISIBILITY
    _Tp fetch_and(_Tp __op, memory_order __m = memory_order_seq_cst) volatile _NOEXCEPT
        {return __c11_atomic_fetch_and(&this->__a_, __op, __m);}
    _LIBCPP_INLINE_VISIBILITY
    _Tp fetch_and(_Tp __op, memory_order __m = memory_order_seq_cst) _NOEXCEPT
        {return __c11_atomic_fetch_and(&this->__a_, __op, __m);}
    _LIBCPP_INLINE_VISIBILITY
    _Tp fetch_or(_Tp __op, memory_order __m = memory_order_seq_cst) volatile _NOEXCEPT
        {return __c11_atomic_fetch_or(&this->__a_, __op, __m);}
    _LIBCPP_INLINE_VISIBILITY
    _Tp fetch_or(_Tp __op, memory_order __m = memory_order_seq_cst) _NOEXCEPT
        {return __c11_atomic_fetch_or(&this->__a_, __op, __m);}
    _LIBCPP_INLINE_VISIBILITY
    _Tp fetch_xor(_Tp __op, memory_order __m = memory_order_seq_cst) volatile _NOEXCEPT
        {return __c11_atomic_fetch_xor(&this->__a_, __op, __m);}
    _LIBCPP_INLINE_VISIBILITY
    _Tp fetch_xor(_Tp __op, memory_order __m = memory_order_seq_cst) _NOEXCEPT
        {return __c11_atomic_fetch_xor(&this->__a_, __op, __m);}

    //已重载的运算符
    _LIBCPP_INLINE_VISIBILITY
    _Tp operator++(int) volatile _NOEXCEPT      {return fetch_add(_Tp(1));}
    _LIBCPP_INLINE_VISIBILITY
    _Tp operator++(int) _NOEXCEPT               {return fetch_add(_Tp(1));}
    _LIBCPP_INLINE_VISIBILITY
    _Tp operator--(int) volatile _NOEXCEPT      {return fetch_sub(_Tp(1));}
    _LIBCPP_INLINE_VISIBILITY
    _Tp operator--(int) _NOEXCEPT               {return fetch_sub(_Tp(1));}
    _LIBCPP_INLINE_VISIBILITY
    _Tp operator++() volatile _NOEXCEPT         {return fetch_add(_Tp(1)) + _Tp(1);}
    _LIBCPP_INLINE_VISIBILITY
    _Tp operator++() _NOEXCEPT                  {return fetch_add(_Tp(1)) + _Tp(1);}
    _LIBCPP_INLINE_VISIBILITY
    _Tp operator--() volatile _NOEXCEPT         {return fetch_sub(_Tp(1)) - _Tp(1);}
    _LIBCPP_INLINE_VISIBILITY
    _Tp operator--() _NOEXCEPT                  {return fetch_sub(_Tp(1)) - _Tp(1);}
    _LIBCPP_INLINE_VISIBILITY
    _Tp operator+=(_Tp __op) volatile _NOEXCEPT {return fetch_add(__op) + __op;}
    _LIBCPP_INLINE_VISIBILITY
    _Tp operator+=(_Tp __op) _NOEXCEPT          {return fetch_add(__op) + __op;}
    _LIBCPP_INLINE_VISIBILITY
    _Tp operator-=(_Tp __op) volatile _NOEXCEPT {return fetch_sub(__op) - __op;}
    _LIBCPP_INLINE_VISIBILITY
    _Tp operator-=(_Tp __op) _NOEXCEPT          {return fetch_sub(__op) - __op;}
    _LIBCPP_INLINE_VISIBILITY
    _Tp operator&=(_Tp __op) volatile _NOEXCEPT {return fetch_and(__op) & __op;}
    _LIBCPP_INLINE_VISIBILITY
    _Tp operator&=(_Tp __op) _NOEXCEPT          {return fetch_and(__op) & __op;}
    _LIBCPP_INLINE_VISIBILITY
    _Tp operator|=(_Tp __op) volatile _NOEXCEPT {return fetch_or(__op) | __op;}
    _LIBCPP_INLINE_VISIBILITY
    _Tp operator|=(_Tp __op) _NOEXCEPT          {return fetch_or(__op) | __op;}
    _LIBCPP_INLINE_VISIBILITY
    _Tp operator^=(_Tp __op) volatile _NOEXCEPT {return fetch_xor(__op) ^ __op;}
    _LIBCPP_INLINE_VISIBILITY
    _Tp operator^=(_Tp __op) _NOEXCEPT          {return fetch_xor(__op) ^ __op;}
};
struct __atomic_base<_Tp, true>
    : public __atomic_base<_Tp, false>
{
    typedef __atomic_base<_Tp, false> __base;
    _LIBCPP_INLINE_VISIBILITY
    __atomic_base() _NOEXCEPT _LIBCPP_DEFAULT
    _LIBCPP_INLINE_VISIBILITY
    _LIBCPP_CONSTEXPR __atomic_base(_Tp __d) _NOEXCEPT : __base(__d) {}
    
    //成员函数
    _LIBCPP_INLINE_VISIBILITY
    _Tp fetch_add(_Tp __op, memory_order __m = memory_order_seq_cst) volatile _NOEXCEPT
        {return __c11_atomic_fetch_add(&this->__a_, __op, __m);}
    _LIBCPP_INLINE_VISIBILITY
    _Tp fetch_add(_Tp __op, memory_order __m = memory_order_seq_cst) _NOEXCEPT
        {return __c11_atomic_fetch_add(&this->__a_, __op, __m);}
    _LIBCPP_INLINE_VISIBILITY
    _Tp fetch_sub(_Tp __op, memory_order __m = memory_order_seq_cst) volatile _NOEXCEPT
        {return __c11_atomic_fetch_sub(&this->__a_, __op, __m);}
    _LIBCPP_INLINE_VISIBILITY
    _Tp fetch_sub(_Tp __op, memory_order __m = memory_order_seq_cst) _NOEXCEPT
        {return __c11_atomic_fetch_sub(&this->__a_, __op, __m);}
    _LIBCPP_INLINE_VISIBILITY
    _Tp fetch_and(_Tp __op, memory_order __m = memory_order_seq_cst) volatile _NOEXCEPT
        {return __c11_atomic_fetch_and(&this->__a_, __op, __m);}
    _LIBCPP_INLINE_VISIBILITY
    _Tp fetch_and(_Tp __op, memory_order __m = memory_order_seq_cst) _NOEXCEPT
        {return __c11_atomic_fetch_and(&this->__a_, __op, __m);}
    _LIBCPP_INLINE_VISIBILITY
    _Tp fetch_or(_Tp __op, memory_order __m = memory_order_seq_cst) volatile _NOEXCEPT
        {return __c11_atomic_fetch_or(&this->__a_, __op, __m);}
    _LIBCPP_INLINE_VISIBILITY
    _Tp fetch_or(_Tp __op, memory_order __m = memory_order_seq_cst) _NOEXCEPT
        {return __c11_atomic_fetch_or(&this->__a_, __op, __m);}
    _LIBCPP_INLINE_VISIBILITY
    _Tp fetch_xor(_Tp __op, memory_order __m = memory_order_seq_cst) volatile _NOEXCEPT
        {return __c11_atomic_fetch_xor(&this->__a_, __op, __m);}
    _LIBCPP_INLINE_VISIBILITY
    _Tp fetch_xor(_Tp __op, memory_order __m = memory_order_seq_cst) _NOEXCEPT
        {return __c11_atomic_fetch_xor(&this->__a_, __op, __m);}

    //已重载的运算符
    _LIBCPP_INLINE_VISIBILITY
    _Tp operator++(int) volatile _NOEXCEPT      {return fetch_add(_Tp(1));}
    _LIBCPP_INLINE_VISIBILITY
    _Tp operator++(int) _NOEXCEPT               {return fetch_add(_Tp(1));}
    _LIBCPP_INLINE_VISIBILITY
    _Tp operator--(int) volatile _NOEXCEPT      {return fetch_sub(_Tp(1));}
    _LIBCPP_INLINE_VISIBILITY
    _Tp operator--(int) _NOEXCEPT               {return fetch_sub(_Tp(1));}
    _LIBCPP_INLINE_VISIBILITY
    _Tp operator++() volatile _NOEXCEPT         {return fetch_add(_Tp(1)) + _Tp(1);}
    _LIBCPP_INLINE_VISIBILITY
    _Tp operator++() _NOEXCEPT                  {return fetch_add(_Tp(1)) + _Tp(1);}
    _LIBCPP_INLINE_VISIBILITY
    _Tp operator--() volatile _NOEXCEPT         {return fetch_sub(_Tp(1)) - _Tp(1);}
    _LIBCPP_INLINE_VISIBILITY
    _Tp operator--() _NOEXCEPT                  {return fetch_sub(_Tp(1)) - _Tp(1);}
    _LIBCPP_INLINE_VISIBILITY
    _Tp operator+=(_Tp __op) volatile _NOEXCEPT {return fetch_add(__op) + __op;}
    _LIBCPP_INLINE_VISIBILITY
    _Tp operator+=(_Tp __op) _NOEXCEPT          {return fetch_add(__op) + __op;}
    _LIBCPP_INLINE_VISIBILITY
    _Tp operator-=(_Tp __op) volatile _NOEXCEPT {return fetch_sub(__op) - __op;}
    _LIBCPP_INLINE_VISIBILITY
    _Tp operator-=(_Tp __op) _NOEXCEPT          {return fetch_sub(__op) - __op;}
    _LIBCPP_INLINE_VISIBILITY
    _Tp operator&=(_Tp __op) volatile _NOEXCEPT {return fetch_and(__op) & __op;}
    _LIBCPP_INLINE_VISIBILITY
    _Tp operator&=(_Tp __op) _NOEXCEPT          {return fetch_and(__op) & __op;}
    _LIBCPP_INLINE_VISIBILITY
    _Tp operator|=(_Tp __op) volatile _NOEXCEPT {return fetch_or(__op) | __op;}
    _LIBCPP_INLINE_VISIBILITY
    _Tp operator|=(_Tp __op) _NOEXCEPT          {return fetch_or(__op) | __op;}
    _LIBCPP_INLINE_VISIBILITY
    _Tp operator^=(_Tp __op) volatile _NOEXCEPT {return fetch_xor(__op) ^ __op;}
    _LIBCPP_INLINE_VISIBILITY
    _Tp operator^=(_Tp __op) _NOEXCEPT          {return fetch_xor(__op) ^ __op;}
};

    volatile同样的道理,num1++语句设计2个操作,一个是读出num1的值,另外一个是写入num1++后的新值,volatile只能保证2个线程之前写入的值对于另外一个线程的可见性:点击打开链接,防止伪共享的产生:点击打开链接,并但是不能保证先读后写这两个操作的原子性,更不能保证代码块的原子性(atomic也不能保证这点),所以当我们仅仅是get和set时可以用volatile来保证数据线程安全,但是如果get后有操作才set那么此时就要用volatile来保证了,如果你需要保证volatile变量和其他无关的读写顺序不被cpu乱序执行,那么在多核环境下volatile没有任何作用,它仅仅保证编译器级别的乱序保证,所以。

   1) 与平台无关的多线程程序,volatile几乎无用(Java的volatile除外);
   2) volatile不保证原子性(一般需使用CPU提供的LOCK指令);
   3) volatile不保证执行顺序;
   4) volatile不提供内存屏障(Memory Barrier)和内存栅栏(Memory Fence)揭开内存屏障的面纱
   5) 多核环境中内存的可见性和CPU执行顺序不能通过volatile来保障,而是依赖于CPU内存屏障。
   注:volatile诞生于单CPU核心时代,为保持兼容,一直只是针对编译器的,对CPU无影响。

      注意,原因1中把java排除了,因为不同编程语言中voldatile含义与实现并不完全相同,Java语言中voldatile变量可以被看作是一种轻量级的同步,因其还附带了acuire和release语义。实际上也是从JDK5以后才通过这个措施进行完善,其volatile 变量具有 synchronized 的可见性特性,但是不具备原子特性。Java语言中有volatile修饰的变量,赋值后多执行了一个“load addl $0x0, (%esp)”操作,这个操作相当于一个lock指令,就是增加一个完全的内存屏障指令,这点与C++实现并不一样,volatile 的读性能消耗与普通变量几乎相同,但是写操作稍慢,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行。
Java实践中仅满足下面这些条件才应该使用volatile关键字:
    1) 变量写入操作不依赖变量当前值,或确保只有一个线程更新变量的值(Java可以,C++仍然不能)
    2)该变量不会与其他变量一起纳入
    3) 变量并未被锁保护
    C++中voldatile等于插入编译器级别屏障,因此并不能阻止CPU硬件级别导致的重排。C++11 中volatile语义没有任何变化,不过提供了std::atomic工具可以真正实现原子操作,而且默认加入了内存屏障(可以通过在store与load操作时设置内存模型参数进行调整,默认为std::memory_order_seq_cst)C++实践中推荐涉及并发问题都使用std::atomic,只有涉及特殊内存操作的时候才使用volatile关键字。这些情况通常IO相关,防止相关操作被编译器优化,也是volatile关键字发明的本意。

    最后再说一下关于自定义类型的atomic,atomic是一个模版,我们可以实现自定义的原子类型,但是是有条件的。为了使用 std::atomic<UDT> (UDT是用户定义类型), 这个类型必须有拷贝赋值运算符。 这就意味着这个类型不能有任何虚函数或虚基类, 以及必须使用编译器创建的拷贝赋值操作。 不仅仅是这些, 自定义类型中所有的基类和非静态数据成员也都需要支持拷贝赋值操作。 这(基本上)就允许编译器使用memcpy(), 或赋值操作的等价操作, 因为它们的实现中没有用户代码。这个类型必须是“位可比的”(bitwise equality comparable)。 这与对赋值的要求差不多;你不仅需要确定, 一个UDT类型对象可以使用memcpy()进行拷贝, 还要确定其对象可以使用memcmp()对位进行比较。 之所以要求这么多, 是为了保证“比较/交换”操作能正常的工作。

   不要将锁定区域内的数据, 以引用或指针的形式, 作为参数传递给用户提供的函数。 通常情况下, 编译器不会为 std::atomic<UDT> 类型生成无锁代码, 所以它将对所有操作使用一个内部锁。 如果用户提供的拷贝赋值或比较操作被允许, 那么这就需要传递保护数据的引用作为一个参数, 这就有悖于指导意见了。 当原子操作需要时, 运行库也可自由的使用单锁, 并且运行库允许用户提供函数持有锁, 这样就有可能产生死锁(或因为做一个比较操作, 而组设了其他的线程)。 最终, 因为这些限制可以让编译器将用户定义的类型看作为一组原始字节, 所以编译器可以对 std::atomic<UDT> 直接使用原子指令(因此实例化一个特殊无锁结构)

  关于这一点也是参考别人的,个人感觉就是几点:

   1:必须有拷贝赋值运算符,必须使用编译器创建的拷贝赋值操作(不是很理解这点)

   2:不能有虚函数和需基类

   3:成员函数中不能以指针或者引用的形式做参数,活着做返回值

   4:位可比性

 

   

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值