@@ -215,3 +215,83 @@ struct trivial_type {
215
215
2 . ** Load 操作(加载操作)** :可选的内存序包括 ` memory_order_relaxed ` 、` memory_order_consume ` 、` memory_order_acquire ` 、` memory_order_seq_cst ` 。
216
216
217
217
3 . ** Read-modify-write(读-改-写)操作** :可选的内存序包括 ` memory_order_relaxed ` 、` memory_order_consume ` 、` memory_order_acquire ` 、` memory_order_release ` 、` memory_order_acq_rel ` 、` memory_order_seq_cst ` 。
218
+
219
+ ## ` st::atomic_flag `
220
+
221
+ ` std::atomic_flag ` 是最简单的原子类型,这个类型的对象可以在两个状态间切换:** 设置(true)** 和** 清除(false)** 。它很简单,通常只是用作构建一些库设施,不会单独使用或直接面向普通开发者。
222
+
223
+ 在 C++20 之前,` std::atomic_flag ` 类型的对象需要以 [ ` ATOMIC_FLAG_INIT ` ] ( https://zh.cppreference.com/w/cpp/atomic/ATOMIC_FLAG_INIT ) 初始化,可以确保此时对象处于
224
+ "清除"(false)状态。
225
+
226
+ ``` cpp
227
+ std::atomic_flag f = ATOMIC_FLAG_INIT;
228
+ ```
229
+
230
+ 在 ` C++20 ` 中 ` std::atomic_flag ` 的默认[ 构造函数] ( https://zh.cppreference.com/w/cpp/atomic/atomic_flag/atomic_flag ) 保证对象为“清除”(false)状态,就不再需要使用 ` ATOMIC_FLAG_INIT ` 。
231
+
232
+ ` ATOMIC_FLAG_INIT ` 其实并不是什么复杂的东西,它在不同的标准库实现中只是简单的初始化:在 [ ` MSVC STL ` ] ( https://github.com/microsoft/STL/blob/daeb0a6/stl/inc/atomic#L2807-L2808 ) 它只是一个 ` {} ` ,在 [ ` libstdc++ ` ] ( https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/bits/atomic_base.h ) 与 [ ` libc++ ` ] ( https://github.com/llvm/llvm-project/blob/00e80fb/clang/lib/Headers/stdatomic.h#L169 ) 它只是一个 ` { 0 } ` 。也就是说我们可以这样初始化:
233
+
234
+ ``` cpp
235
+ std::atomic_flag f ATOMIC_FLAG_INIT;
236
+ std::atomic_flag f2 = {};
237
+ std::atomic_flag f3{};
238
+ std::atomic_flag f4{ 0 };
239
+ ```
240
+
241
+ 使用 ATOMIC_FLAG_INIT 宏只是为了统一,我们知道即可。
242
+
243
+ 当标志对象已初始化,它只能做三件事情:**销毁、清除、设置**。这些操作对应的函数分别是:
244
+
245
+ 1. **`clear()`** (清除):将标志对象的状态原子地更改为清除(false)
246
+ 2. **`test_and_set`**(测试并设置):将标志对象的状态原子地更改为设置(true),并返回它先前保有的值。
247
+ 3. **销毁**:对象的生命周期结束时,自动调用析构函数进行销毁操作。
248
+
249
+ 每个操作都可以指定内存顺序。`clear()` 是一个“读-改-写”操作,可以应用任何内存顺序。默认的内存顺序是 `memory_order_seq_cst`。例如:
250
+
251
+ ```cpp
252
+ f.clear(std::memory_order_release);
253
+ bool r = f.test_and_set();
254
+ ```
255
+
256
+ 1 . 将 ` f ` 的状态原子地更改为清除(false),指明 ` memory_order_release ` 内存序。
257
+
258
+ 2 . 将 ` f ` 的状态原子地更改为设置(true),并返回它先前保有的值给 ` r ` 。使用默认的 ` memory_order_seq_cst ` 内存序。
259
+
260
+ > 不用着急,这里还不是详细展开聊内存序的时候。
261
+
262
+ ` std::atomic_flag ` [ 不可复制] ( https://zh.cppreference.com/w/cpp/atomic/atomic_flag/atomic_flag ) 不可移动[ 不可赋值] ( https://zh.cppreference.com/w/cpp/atomic/atomic_flag/operator%3D ) 。这不是 ` std::atomic_flag ` 特有的,而是所有原子类型共有的属性。原子类型的所有操作都是原子的,而赋值和拷贝涉及两个对象,破坏了操作的原子性。拷贝构造和拷贝赋值会先读取第一个对象的值,然后再写入另一个对象。对于两个独立的对象,这里实际上有两个独立的操作,合并这两个操作无法保证其原子性。因此,这些操作是不被允许的。
263
+
264
+ 有限的特性使得 ` std::atomic_flag ` 非常适合用作制作** 自旋锁** 。
265
+
266
+ ``` cpp
267
+ class spinlock_mutex {
268
+ std::atomic_flag flag{};
269
+ public:
270
+ void lock() {
271
+ while (flag.test_and_set(std::memory_order_acquire));
272
+ }
273
+
274
+ void unlock() {
275
+ flag.clear(std::memory_order_release);
276
+ }
277
+ };
278
+ ```
279
+
280
+ 我们可以简单的使用测试一下,它是有效的:
281
+
282
+ ``` cpp
283
+ spinlock_mutex m;
284
+
285
+ void f (){
286
+ std::lock_guard<spinlock_mutex> lc{ m };
287
+ std::cout << "😅😅" << "❤️❤️\n";
288
+ }
289
+ ```
290
+
291
+ > [ 运行] ( https://godbolt.org/z/9rs9sxsns ) 测试。
292
+
293
+ 稍微聊一下原理,我们的 ` spinlock_mutex ` 对象中存储的 ` flag ` 对象在默认构造时是清除 (` false ` ) 状态。在 ` lock() ` 函数中调用 ` test_and_set ` 函数,它是原子的,只有一个线程能成功调用并将 ` flag ` 的状态原子地更改为设置 (` true ` ),并返回它先前的值 (` false ` )。此时,该线程成功获取了锁,退出循环。
294
+
295
+ 当 ` flag ` 对象的状态为设置 (` true ` ) 时,其线程调用 ` test_and_set ` 函数会返回 ` true ` ,导致它们继续在循环中自旋,无法退出。直到先前持有锁的线程调用 ` unlock() ` 函数,将 ` flag ` 对象的状态原子地更改为清除 (` false ` ) 状态。此时,等待的线程中会有一个线程成功调用 ` test_and_set ` 返回 ` false ` ,然后退出循环,成功获取锁。
296
+
297
+ ` std::atomic_flag ` 的局限性太强,甚至不能当普通的 bool 标志那样使用。一般最好使用 ` std::atomic<bool> ` ,下节,我们来使用它。
0 commit comments