c++并发编程(五)—— 原子类型与原子操作 atomic
原子操作是一个不可分割的操作,标准c++中通过原子类型来获得原子操作。
从一个简单的例子来看看使用原子类型的作用:
std::atomic<int> sum = 0;
//int sum = 0;
void fun()
{
for (int i = 0; i<100000; ++i)
sum ++; //使用原子类型对sum的操作是原子的
}
int main()
{
std::cout << "Before joining,sun = " << sum << std::endl;
std::thread t1(fun);
std::thread t2(fun);
t1.join();
t2.join();
std::cout << "After joining,sun = " << sum << std::endl;
system("pause");
return 0;
}
可以看到使用原子类型可以完成无锁的线程间数据共享。
从最简单的标准原子类型std::atomic_flag讲起
//使用std::atomic_flag的自旋锁互斥实现
class spinlock_mutex {
std::atomic_flag flag = ATOMIC_FLAG_INIT;
public:
spinlock_mutex() {}
void lock() {
while (flag.test_and_set(std::memory_order_acquire));
}
void unlock() {
flag.clear(std::memory_order_release);
}
};
eg:c++标准并未提供自旋锁。自旋锁是一种用于保护多线程共享资源的锁,与一般的互斥锁(mutex)不同之处在于当自旋锁尝试获取锁的所有权时会以忙等待(busy waiting)的形式不断的循环检查锁是否可用。在多处理器环境中对持有锁时间较短的程序来说使用自旋锁代替一般的互斥锁往往能提高程序的性能。
其中灰色方框中的3步应是一个不可分割的原子操作。
释放自旋锁时只需以原子操作的形式将flag置为false。
std::atomic_flag对象只包含两种操作——test_and_set()&clear()——并且都是原子操作。
test_and_set()是一个读-修改-写操作,flag为false时写成功则返回true;
clear()是一个存储操作,将flag设置为false;
std::atomic<T>
对基本类型(如int、double *等)使用std::atomic<T>模板包装成原子类型,可使用的原子操作有:load、store、exchange、compare等。
原子操作的内存顺序
为优化程序代码,编译器和处理器可以*地对任何无相互依赖关系的命令进行重新排列。例如,两个分配语句 a=0;B=1;,它们可以以任一顺序执行。然而,在多线程环境下,由于不同线程内存操作之间的依赖性对于编译器或处理器通常是不可见的,所以对编译器或处理器执行命令重新排序可能会引发错误。通过制定原子操作的内存顺序来减弱线程间原子上下文同步的耦合性来增加执行效率。
通过指定内存序代替默认的std::memory_order_seq_cst可以提高性能。C++11的std::memory_order有6个枚举值。
Value | 描述 | |
---|---|---|
std::memory_order_relaxed | 没有任何同步和排序限制,只需保证操作是原子的。 | |
std::memory_order_consume | 一个指定了此值的load操作在受影响的内存位置上执行consume操作,此操作使得另一个在同一内存位置执行了release操作的线程在此之前对数据依赖(data-dependent)的内存位置的写操作为当前线程可见。 | |
std::memory_order_acquire | 一个指定了此值的load操作在受影响的内存位置上执行acquire操作,此操作使得另一个在同一内存位置执行了release操作的线程在此之前对任意的内存位置的写操作为当前线程可见。 | |
std::memory_order_release | 一个指定了此值的store操作在受影响的内存位置上执行release操作,此操作使得此线程之前对数据依赖(data-dependent)的内存位置的写操作为另一个在此之后通过对同一内存位置执行consume操作的线程可见,使得此线程之前对任意的内存位置的写操作为另一个在此之后通过对同一个内存位置执行acquire操作的线程可见。 | |
std::memory_order_acq_rel | 一个指定了此值的read-modify-write操作,在读阶段(相当于load)对受影响的内存位置执行acquire操作,在写阶段(相当于store)对同一内存位置执行release操作。 | |
std::memory_order_seq_cst | 顺序一致性,所有的线程观察到的整个程序中内存修改顺序是一致的。 |
顺序一致性顺序std::memory_order_seq_cst
在线程a中#1操作在#2之前,使用顺序一致性顺序,在线程b中也视为这个顺序。也就是说顺序一致要求所有的线程之间的全局同步。
std::atomic<int> data;
std::atomic<bool> data_ready(false);
// 线程a
void writer_thread()
{
data.store(10,std::memory_order_seq_cst); // #1:对data的写操作
data_ready.store(true, std::memory_order_seq_cst); // #2:对data_ready的写操作
}
// 线程b
void reader_thread()
{
while (!data_ready.load(std::memory_order_seq_cst)) {} // #3:对data_ready的读操作
std::cout << "data is "<< data.load(std::memory_order_seq_cst) << "\n"; // #4:对data的读操作
}
int main() {
std::thread a(reader_thread);
std::thread b(writer_thread);
a.join();
b.join();
system("pause");
}
松散顺序std::memory_order_relaxed
此时在线程b中,操作#1与操作#2的顺序不定,可能data_ready为true时data还为0
std::atomic<int> data;
std::atomic<bool> data_ready(false);
// 线程a
void writer_thread()
{
data.store(10,std::memory_order_relaxed); // #1:对data的写操作
data_ready.store(true, std::memory_order_relaxed); // #2:对data_ready的写操作
}
// 线程b
void reader_thread()
{
while (!data_ready.load(std::memory_order_relaxed)) {} // #3:对data_ready的读操作
std::cout << "data is "<< data.load(std::memory_order_relaxed) << "\n"; // #4:对data的读操作
}
int main() {
std::thread b(writer_thread);
std::thread a(reader_thread);
a.join();
b.join();
system("pause");
}
获取——释放顺序std::memory_order_acquire&std::memory_order_release
因为松散顺序太过*,顺序一致性顺序又太过严格,这里提供了获取释放顺序
std::atomic<int> data;
std::atomic<bool> data_ready(false);
// 线程a
void writer_thread()
{
data.store(10,std::memory_order_relaxed); // #1:对data的写操作
data_ready.store(true, std::memory_order_release); // #2:对data_ready的写操作
}
// 线程b
void reader_thread()
{
while (!data_ready.load(std::memory_order_acquire)) {} // #3:对data_ready的读操作
std::cout << "data is "<< data.load(std::memory_order_relaxed) << "\n"; // #4:对data的读操作
}
int main() {
std::thread b(writer_thread);
std::thread a(reader_thread);
a.join();
b.join();
system("pause");
}
std::memory_order_consume
有时你会觉得获取释放顺序也有点严格,我只是想保证#2在#3的之前却连带着使#1在#4之前了,这时使用std::memory_order_consume
std::atomic<int> data;
std::atomic<bool> data_ready(false);
// 线程a
void writer_thread()
{
data_ready.store(true, std::memory_order_relaxed); // #2:对data_ready的写操作
data.store(10, std::memory_order_relaxed); // #1:对data的写操作
}
// 线程b
void reader_thread()
{
while (!data_ready.load(std::memory_order_consume)) {} // #3:对data_ready的读操作
std::cout << "data is "<< data.load(std::memory_order_relaxed) << "\n"; // #4:对data的读操作
}
int main() {
std::thread b(writer_thread);
std::thread a(reader_thread);
a.join();
b.join();
system("pause");
}
最后这里有篇说的不错,转载一下:
上一篇: 基于 CNN 的字符识别
下一篇: CMake安装使用