原子操作就是在多线程程序中“最小的且不可并行化的”操作,就是说多个线程访问同一个资源时,有且仅有一个线程能对资源进行操作。
在C++11之前,使用第三方API可以实现并行编程,比如pthread多线程库,但是在使用时需要创建互斥锁,以及进行加锁、解锁等操作来保证多线程对临界资源的原子操作,这无疑增加了开发的工作量。不过从C++11开始,C++从语言层面开始支持并行编程,内容包括了管理线程、保护共享数据、线程间的同步操作、低级原子操作等各种类。新标准极大地提高了程序的可移植性,以前的多线程依赖于具体的平台,而现在有了统一的接口。
std::atomic_flag是最简单的标准原子类型,他代表一个布尔标识,没有拷贝构造函数和拷贝赋值运算符(=delete)。原子类型指的是对它的单个操作一定要在一个指令周期完成。
std::atomic_flag 构造函数
std::atomic_flag 构造函数如下:
- atomic_flag() noexcept = default;
- atomic_flag (const atomic_flag&T) = delete;
std::atomic_flag 只有默认构造函数,拷贝构造函数已被禁用,因此不能从其他的 std::atomic_flag 对象构造一个新的 std::atomic_flag 对象。
如果在初始化时没有明确使用 ATOMIC_FLAG_INIT初始化,那么新创建的 std::atomic_flag 对象的状态是未指定的(unspecified)(既没有被 set 也没有被 clear。)另外,atomic_flag不能被拷贝,也不能 move 赋值。
ATOMIC_FLAG_INIT: 如果某个 std::atomic_flag 对象使用该宏初始化,那么可以保证该 std::atomic_flag 对象在创建时处于 clear 状态。
std::atomic_flag::test_and_set
std::atomic_flag 的 test_and_set 函数原型如下:
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() 函数检查 std::atomic_flag 标志,如果 std::atomic_flag 之前没有被设置过,则设置 std::atomic_flag 的标志,并返回先前该 std::atomic_flag 对象是否被设置过,如果之前 std::atomic_flag 对象已被设置,则返回 true,否则返回 false。
自旋锁实现
class spin_mutex {
std::atomic_flag flag = ATOMIC_FLAG_INIT;
public:
spin_mutex() = default;
spin_mutex(const spin_mutex&) = delete;
spin_mutex& operator= (const spin_mutex&) = delete;
void lock() {
while(flag.test_and_set(std::memory_order_acquire))
;
}
void unlock() {
flag.clear(std::memory_order_release);
}
};
memory_order_acquire, memory_order_release是用来指定内存序。从代码中可以看到,如果flag已经被其它线程设置,那么就相当于自旋锁已经被其它线程占有,那么就会一直while循环检查flag,直到其它线程释放flag。