一、我们要先搞明白什么叫原子操作?使用这个东西有什么目的?
原子操作:能够操作最接近机器的指令,这个和硬件相关了,虽然和硬件相关,但我们的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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
结果:每隔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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
结果:每隔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关系。我找不到更好的办法来描述书上的内容了。
原文链接:https://blog.csdn.net/what951006/article/details/78273903