C++ 原子操作(6种原子顺序)

一、我们要先搞明白什么叫原子操作?使用这个东西有什么目的?

原子操作:能够操作最接近机器的指令,这个和硬件相关了,虽然和硬件相关,但我们的C11还是整合了这一切,让原子操作有了共同的调用接口
目的:使用这个的目的说实话,就是让你更了解机器已及多线程同步的原理和秘密,当然有一些需求较简单的,使用原子操作可能比封装好的更有效率!!用了百遍的mutex可能你现在还不知道他们是怎么互斥的~当然内部还是通过了原子操作来的!


二、讲讲原理

原子操作只有2种状态,一种是没做,一种是做完了,看不到正在做的状态,这个是在任何线程下都满足这个要求,当两个线程同时访问一块内存的时候,如果有任何一个在写,那肯定会产生竞争,如果两个同时读,没有问题,那如何用原子操作来控制不产生竞争呢?可以这样来想,当两个线程在访问的时候,一定有一个先后顺序,谁先访问,谁后访问,这就是修改顺序,我们要在任何线程可以看到这样的顺序!然后就可以通过一定的逻辑来处理并发竞争的情况了!


三、标准库操作

例:我们一实现一个线程读取,一个线程写入,当然,要先写入才能读取,所以这个是顺序问题。

方法一:condition_variable来操作
/*
使用condition_variable,读线程wait,直到写线程调用 notify_all,即停止等待
*/
#include <thread>
#include <condition_variable>
#include <iostream>
using namespace std;

condition_variable g_CV;
mutex g_mtx;
void read_thread()
{
    while (true)
    {
        unique_lock<mutex> ul(g_mtx);
        g_CV.wait(ul);
        cout << g_value << endl;
    }
}
void write_thread()
{
    while (true)
    {
        Sleep(1000);
        lock_guard<mutex> lg(g_mtx);
        g_value++;
        g_CV.notify_all();
    }
}
int main()
{
    thread th(read_thread);
    th.detach();
    thread th2(write_thread);
    th2.detach();
    char a;
    while (cin >> a);
    return 0;
}

结果:每隔1秒打印
这里写图片描述

方法二:使用标准原子操作
/*使用原子操作来控制线程顺序,实现了一个类似于互斥锁这么一个概念*/
#include <thread>
#include <atomic>
#include <iostream>
using namespace std;

int g_value(0);
atomic<bool> g_data_ready(false);

void read_thread()
{
    while (true)
    {
        while (!g_data_ready.load());//用于控制进入
        cout << g_value << endl;
        g_data_ready = false;
    }
}

void write_thread()
{
    while (true)
    {
        while (g_data_ready.load());//用于控制进入
        Sleep(1000);
        g_value++;
        g_data_ready = true;
    }
}

int main()
{
    thread th(read_thread);
    th.detach();
    thread th2(write_thread);
    th2.detach();
    char a;
    while (cin >> a);
    return 0;
}

结果:每隔1秒打印
同样


四、六种原子操作内存顺序

这个比较的头痛了,对于刚接触这个概念的时候,我也是一头雾水。。但随着我各种查资料,慢慢就有所理解,知乎的一位答主讲的比较不错,点此,再加之深究《C++并发编程实战》第五章的内容。

//这里枚举这六种
typedef enum memory_order {
    memory_order_relaxed, 
    memory_order_consume, 
    memory_order_acquire, 
    memory_order_release,
    memory_order_acq_rel,
    memory_order_seq_cst 
} memory_order;
  • memory_order_seq_cst :这个是默认的原子顺序,即按代码怎么写的就是怎么个顺序!

  • memory_order_relaxed:这个是松散顺序,《C++并发编程实战》第5章 123页举的例子讲的很清楚,鉴于篇幅,我也简单陈述一下,举书本上的例子:

#include <atomic>
#include <thread>
#include <assert.h>

std::atomic<bool> x,y;
std::atomic<int> z;

void write_x_then_y()
{
    x.store(true,std::memory_order_relaxed);    #1
    y.store(true,std::memory_order_relaxed);    #2
}

void read_y_then_x()
{
    while(!y.load(std::memory_order_relaxed));  #3
    if(x.load(std::memory_order_relaxed))       #4
        ++z;
}

int main()
{
    x=false;
    y=false;
    z=0;
    std::thread a(write_x_then_y);
    std::thread b(read_y_then_x);
    a.join();
    b.join();
    assert(z.load()!=0);                        #5//断言发生,z是可能等于0的
}

为什么断言可能发生?意思是z可能为0,x可能为0,这个问题,就是relaxed的锅, 在write_x_then_y线程中,因为#1,#2的store是松散的,在read_y_then_x线程中,也是以松散来读的,x与y没有必然的联系,意思是x.load的时候,可能返回false,编译器或者硬件可随便更改线程中的顺序,所以说慎用使用松散顺序!还有就是这种是理想条件下的,至少x86Cpu目前没有该功能!

  • memory_order_release,memory_order_acquire:我把这个放在一起,因为这两个是一套的,要搭配使用,举个例子来
#include <atomic>
#include <thread>
#include <assert.h>
std::atomic<bool> x,y;
std::atomic<int> z;
void write_x()
{
     x.store(true,std::memory_order_release);
}
void write_y()
{
     y.store(true,std::memory_order_release);
}
void read_x_then_y()
{
     while(!x.load(std::memory_order_acquire));
     if(y.load(std::memory_order_acquire))//y为false;
        ++z;
}
void read_y_then_x()
{
     while(!y.load(std::memory_order_acquire));
     if(x.load(std::memory_order_acquire))//x为false;
        ++z;
}
int main()
{
     x=false;
     y=false;
     z=0;
     std::thread a(write_x);
     std::thread b(write_y);
     std::thread c(read_x_then_y);
     std::thread d(read_y_then_x);
     a.join();
     b.join();
     c.join();
     d.join();
     assert(z.load()!=0);
}

z这次会触发,意思是z=0,原因4个线程相互独立,release与acquire是一种同步的搭配,但他们必须配对,如果不配对,就像relaxed一样,返回先前的值。

  • memory_order_release,memory_order_consume 这两个也是一对的,配对使用才能同步,与acquire区别在这里
struct X
{
     int i;
     std::string s;
};
std::atomic<X*> p;
std::atomic<int> a;
void create_x()
{
     X* x=new X;
     x->i=42;
     x->s=”hello”;
     a.store(99,std::memory_order_relaxed);  //   1
     p.store(x,std::memory_order_release);
}
void use_x()
{
     X* x;
     while(!(x=p.load(std::memory_order_consume))) // 2
     std::this_thread::sleep(std::chrono::microseconds(1));
     assert(x->i==42);
     assert(x->s==”hello”);
     assert(a.load(std::memory_order_relaxed)==99); //3
}
int main()
{
     std::thread t1(create_x);
     std::thread t2(use_x);
     t1.join();
     t2.join();
}

如果把位置2的consume 换成acquire,那他们是同步的,3虽然是relaxed,但被迫同步了!a.load会等于3,如果像上面这样的代码,a.load不会等于3,意思是consume的release的前不保证happend-before关系。我找不到更好的办法来描述书上的内容了。


未完待续!
更多文章:http://blog.csdn.net/what951006?viewmode=list
powered by:小乌龟在大乌龟背上~

  • 24
    点赞
  • 167
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值