漫话 C++11 之 atomic_flag

原子操作

原子操作,是多线程环境下的一个重要概念,是指它是否在共享内存中完成了一个线程相关的单步操作。

  • 当一个原子存储作用于一个共享变量时,其他的线程不能监测到这个未完成的修改值。
  • 当一个原子加载作用于一个共享变量时,它读取到这个完整的值,就像此时出现了一个单独的时刻。

而非原子加载和存储则不能做到上述两点保证。

为了保证数据的最终一致性,我们可以定义如下规则:

任何时刻两个线程同时操作一个共享变量,当其中一个为写操作时,这两个线程必须使用原子操作。

atomic_flag

atomic_flag 是一种简单的原子布尔类型,只支持两种操作:test-and-setclear

构造函数

atomic_flag 只有默认构造函数,拷贝构造函数已被禁用,因此不能从其他的 atomic_flag 对象构造。

1
2
atomic_flag() noexcept = default;
atomic_flag (const atomic_flag&T) = delete;

如果在初始化时没有明确使用 ATOMIC_FLAG_INIT 初始化,那么新创建的 atomic_flag 对象的状态是未指定的(unspecified)(既没有被 set 也没有被 clear。)另外,atomic_flag 不能被拷贝,也不能 move 赋值。

ATOMIC_FLAG_INIT:

如果某个 atomic_flag 对象使用该宏初始化,那么可以保证该对象在创建时处于 clear 状态。

看一个简单的例子,main() 函数中创建了 10 个线程进行计数,率先完成计数任务的线程输出自己的 ID,后续完成计数任务的线程不会输出自身 ID:

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
#include <iostream>  
#include <atomic>
#include <thread>
#include <vector>

// can be checked without being set
std::atomic<bool> ready (false);

// always set when checked
std::atomic_flag winner = ATOMIC_FLAG_INIT;

void count1m (int id) {
// wait for the ready signal
while (!ready) { std::this_thread::yield(); }

// go!, count to 1 million
for (int i=0; i<1000000; ++i) {}

if (!winner.test_and_set()) {
std::cout << "thread #" << id << " won!\n";
}
};

int main ()
{
std::vector<std::thread> threads;

std::cout << "spawning 10 threads that count to 1 million...\n";
for (int i=1; i<=10; ++i)
threads.push_back(std::thread(count1m,i));

ready = true;
for (auto& th : threads)
th.join();

return 0;
}

test_and_set

test_and_set() 函数检查 atomic_flag 标志:

如果之前没有被设置过,则设置 atomic_flag 的标志,并返回 false

如果之前 atomic_flag 对象已被设置,则返回 true

1
2
bool test_and_set (memory_order sync = memory_order_seq_cst) volatile noexcept;
bool test_and_set (memory_order sync = memory_order_seq_cst) noexcept;

test-and-set 操作是原子(read-modify-write)的,它到当前操作不受其他线程影响。

参数 sync 的取值如下:

Memory Order 值 Memory Order 类型
memory_order_relaxed Relaxed
memory_order_consume Consume
memory_order_acquire Acquire
memory_order_release Release
memory_order_acq_rel Acquire/Release
memory_order_seq_cst Sequentially consistent

这里涉及到有关 C++ 中内存模型 Memory Order 姿势,先挖个坑,日后单写一篇这个主题的文章。

clear

清除 atomic_flag 对象的标志位,设置 atomic_flag 的值为 false

1
2
void clear (memory_order sync = memory_order_seq_cst) volatile noexcept;
void clear (memory_order sync = memory_order_seq_cst) noexcept;

清除标志使得下一次调用 test_and_set 返回 false

自旋锁

结合 test_and_set()clear()atomic_flag 对象可以当作一个简单的自旋锁 使用,请看下例:

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
#include <iostream>
#include <atomic>
#include <thread>
#include <vector>
#include <sstream>

std::atomic_flag lock_stream = ATOMIC_FLAG_INIT;
std::stringstream stream;

void append_number(int x) {
while (lock_stream.test_and_set()) // acquire lock
; //spin
stream << "thread #" << x << '\n';
lock_stream.clear(); // release lock
}

int main ()
{
std::vector<std::thread> threads;

for (int i=1; i<=10; ++i)
threads.push_back(std::thread(append_number,i));

for (auto& th : threads)
th.join();

std::cout << stream.str();
return 0;
}
彦祖老师 wechat