C++ 多线程:原子类型(std::atomic)

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第3天,点击查看活动详情

概念

我们平时编程时很多情况下需要在多个线程间共享一个简单的类型变量(int,bool,pointer等),对这种简单临界资源的访问,如有两个线程,对一个变量进行操作,一个线程读这个变量的值,一个线程往这个变量中写值。即使是一个简单变量的读取和写入操作,如果不加锁,也有可能会导致读写值混乱(因为一条语句可能会被拆成3、4条汇编语句来执行,所以仍然有可能混乱)。正常情况下,我们想到就是使用std::mutex来解决上述对临界资源访问的问题,使用std::mutex程序执行不会导致混乱,但是每一次循环都要加锁解锁是的程序开销很大。 为了提高性能,C++11提供了原子类型(std::atomic<T>),它提供了多线程间的原子操作,可以把原子操作理解成一种:不需要用到互斥量加锁(无锁)技术的多线程并发编程方式。它定义在<atomic>头文件中,原子类型是封装了一个值的类型,它的访问保证不会导致数据的竞争,并且可以用于在不同的线程之间同步内存访问。从效率上来说,原子操作要比互斥量的方式效率要高。互斥量的加锁一般是针对一个代码段,而原子操作针对的一般都是一个变量。 原子操作,一般都是指“不可分割的操作”;是一系列不可被 CPU 上下文交换的机器指令,这些指令组合在一起就形成了原子操作。在多核 CPU 下,当某个 CPU 核心开始运行原子操作时,会先暂停其它 CPU 内核对内存的操作,以保证原子操作不会被其它 CPU 内核所干扰。 由于原子操作是通过指令提供的支持,因此它的性能相比锁和消息传递会好很多。相比较于锁而言,原子类型不需要开发者处理加锁和释放锁的问题,同时支持修改,读取等操作,还具备较高的并发性能,几乎所有的语言都支持原子类型。原子类型是无锁类型,但是无锁不代表无需等待,因为原子类型内部使用了 CAS 循环,当大量的冲突发生时,该等待还是得等待!但是总归比锁要好。

CAS 全称是 Compare and swap, 它通过一条指令读取指定的内存地址,然后判断其中的值是否等于给定的前置值,如果相等,则将其修改为新的值

在介绍std::atomic模板前,我们先来学习 <atomic> 头文件中最简单的原子类型: atomic_flag。

std::atomic_flag

std::atomic_flag 是原子布尔类型。不同于所有 std::atomic 的特化,它保证是免锁的。不同于 std::atomic<bool>std::atomic_flag 不提供加载或存储操作。std::atomic_flag是最简单的原子类型,这个类型的对象可以在两个状态间切换:设置和清除。

类型定义

// atomic_flag类型
struct atomic_flag
{
  atomic_flag() noexcept = default;
  atomic_flag(const atomic_flag&) = delete;
  atomic_flag& operator=(const atomic_flag&) = delete;
  atomic_flag& operator=(const atomic_flag&) volatile = delete;

  bool test_and_set(memory_order = memory_order_seq_cst) volatile noexcept;
  bool test_and_set(memory_order = memory_order_seq_cst) noexcept;
  void clear(memory_order = memory_order_seq_cst) volatile noexcept;
  void clear(memory_order = memory_order_seq_cst) noexcept;
};

bool atomic_flag_test_and_set(volatile atomic_flag*) noexcept;
bool atomic_flag_test_and_set(atomic_flag*) noexcept;
bool atomic_flag_test_and_set_explicit(volatile atomic_flag*, memory_order) noexcept;
bool atomic_flag_test_and_set_explicit(atomic_flag*, memory_order) noexcept;
void atomic_flag_clear(volatile atomic_flag*) noexcept;
void atomic_flag_clear(atomic_flag*) noexcept;
void atomic_flag_clear_explicit(volatile atomic_flag*, memory_order) noexcept;
void atomic_flag_clear_explicit(atomic_flag*, memory_order) noexcept;

#define ATOMIC_FLAG_INIT unspecified
复制代码

默认构造函数

构造一个新std::atomic_flag对象,不过未指明状态。这里未指定默认构造出来的std::atomic_flag实例是clear状态,还是set状态。因为对象存储过程是静态的,所以初始化必须是静态的。std::atomic_flag 使用ATOMIC_FLAG_INIT进行初始化,这样构造出来的实例状态为clear。另外,atomic_flag不能被拷贝,也不能 move 赋值。

std::atomic_flag::test_and_set

自动设置实例状态标识,并且检查实例的状态标识是否已经设置。

std::atomic_flag::clear

自动清除原子变量的状态标识。支持std::memory_order_relaxed,std::memory_order_releasestd::memory_order_seq_cst中任意一个。

示例,成员函数版本:

#include <thread>
#include <vector>
#include <iostream>
#include <atomic>
 
std::atomic_flag lock = ATOMIC_FLAG_INIT;
 
void f(int n)
{
    while (lock.test_and_set(std::memory_order_acquire)) {  // 获得锁
            ; // 自旋
    }
    std::cout << "Output from thread " << n << '\n';
    lock.clear(std::memory_order_release);               // 释放锁
}
 
int main()
{
    std::vector<std::thread> v;
    for (int n = 0; n < 10; ++n) {
        v.emplace_back(f, n);
    }
    for (auto& t : v) {
        t.join();
    }
}
复制代码

可能的输出:

Output from thread 1
Output from thread 2
Output from thread 0
Output from thread 3
Output from thread 5
Output from thread 6
Output from thread 7
Output from thread 8
Output from thread 9
Output from thread 4
复制代码

使用非成员函数版本:

#include <thread>
#include <vector>
#include <iostream>
#include <atomic>
 
std::atomic_flag lock = ATOMIC_FLAG_INIT;
 
void f(int n)
{
    while(std::atomic_flag_test_and_set_explicit(&lock, std::memory_order_acquire)) {
            ; // 自旋直至获得锁
    }
    std::cout << "Output from thread " << n << '\n';
    std::atomic_flag_clear_explicit(&lock, std::memory_order_release);
}
 
int main()
{
    std::vector<std::thread> v;
    for (int n = 0; n < 10; ++n) {
        v.emplace_back(f, n);
    }
    for (auto& t : v) {
        t.join();
    }
}
复制代码

有限的特性使得std::atomic_flag非常适合于作自旋锁。初始化标志是“清除”,并且互斥量处于解锁状态。为了锁上互斥量,循环运行test_and_set()直到旧值为false,就意味着这个线程已经被设置为true了。解锁互斥量是一件很简单的事情,将标志清除即可。

class spinlock_mutex
{
  std::atomic_flag flag;
public:
  spinlock_mutex():
    flag(ATOMIC_FLAG_INIT)
  {}
  void lock()
  {
    while(flag.test_and_set(std::memory_order_acquire));
  }
  void unlock()
  {
    flag.clear(std::memory_order_release);
  }
};
复制代码

由于std::atomic_flag的局限性太强,没有非修改查询操作,甚至不能像普通的布尔标志那样使用。所以,实际操作中最好使用std::atomic<bool>std::atomic<bool>和std::atomic_flag的不同之处在于,std::atomic<bool>可能不是无锁的。为了保证操作的原子性,其实现中可能需要内置的互斥量。特殊情况时,可以使用is_lock_free()成员函数,检查std::atomic<bool>上的操作是否无锁。这是除了std::atomic_flag之外,另一个所有原子类型都拥有的特征(is_lock_free)。

std::atomic类模板

std::atomic模板允许用户使用自定义类型创建一个原子变量(除了标准原子类型之外),需要满足一定的标准才可以使用std::atomic<>。为了使用std::atomic<UDT>(UDT是用户定义类型),这个类型必须有拷贝赋值运算符。这就意味着这个类型不能有任何虚函数或虚基类,以及必须使用编译器创建的拷贝赋值操作。不仅仅是这些,自定义类型中所有的基类和非静态数据成员也都需要支持拷贝赋值操作。这(基本上)就允许编译器使用memcpy()或赋值操作的等价操作,因为实现中没有用户代码。

最终,比较-交换操作操作就类似于memcmp使用位比较,而非为UDT类定义一个比较操作符。如果UDT类型具有对于不同语义的比较操作,或者是这个类型有不参与比较的填充位,那么即使两个对象的值是相等的,也可能导致比较-交换操作失败。

类型定义

template < class T > struct atomic {
    bool is_lock_free() const volatile;
    bool is_lock_free() const;
    void store(T, memory_order = memory_order_seq_cst) volatile;
    void store(T, memory_order = memory_order_seq_cst);
    T load(memory_order = memory_order_seq_cst) const volatile;
    T load(memory_order = memory_order_seq_cst) const;
    operator  T() const volatile;
    operator  T() const;
    T exchange(T, memory_order = memory_order_seq_cst) volatile;
    T exchange(T, memory_order = memory_order_seq_cst);
    bool compare_exchange_weak(T &, T, memory_order, memory_order) volatile;
    bool compare_exchange_weak(T &, T, memory_order, memory_order);
    bool compare_exchange_strong(T &, T, memory_order, memory_order) volatile;
    bool compare_exchange_strong(T &, T, memory_order, memory_order);
    bool compare_exchange_weak(T &, T, memory_order = memory_order_seq_cst) volatile;
    bool compare_exchange_weak(T &, T, memory_order = memory_order_seq_cst);
    bool compare_exchange_strong(T &, T, memory_order = memory_order_seq_cst) volatile;
    bool compare_exchange_strong(T &, T, memory_order = memory_order_seq_cst);
    atomic() = default;
    constexpr atomic(T);
    atomic(const atomic &) = delete;
    atomic & operator=(const atomic &) = delete;
    atomic & operator=(const atomic &) volatile = delete;
    T operator=(T) volatile;
    T operator=(T);
};
复制代码

构造函数

构造新的原子对象。

default (1)	        atomic() noexcept = default;
initialization (2)	constexpr atomic (T val) noexcept;
copy [deleted] (3)	atomic (const atomic&) = delete;
复制代码
  • 1)默认构造函数:原子对象处于未初始化状态,后续可用 std::atomic_init完成初始化。默认初始化的 std::atomic<T> 不含 T 对象,它仅有的合法用法是析构以及后面使用 std::atomic_init 对他进行初始化。
  • 2)以 val 初始化底层值。该初始化非原子。
  • 3)已删除(无法复制/移动原子对象)。

示例:

#include <iostream>
#include <atomic>
 
int main()
{
    std::atomic<int> a;
    std::atomic_init(&a, 1);
    std::cout << a.load() << std::endl;

    std::atomic<int> b(2);
    std::cout << b.load() << std::endl;
    
    std::atomic<int> c(b);  // error
}
复制代码

std::atomic::operator=

set value (1)	 
	T operator= (T val) noexcept;
	T operator= (T val) volatile noexcept;
copy [deleted] (2)	
	atomic& operator= (const atomic&) = delete;
	atomic& operator= (const atomic&) volatile = delete;
复制代码
  1. val 值赋给原子变量。等价于 store(val) 。若 std::atomic<T>::is_always_lock_free 为 false 则 volatile 限定版本被弃用 (C++20 起),该操作是原子的,内存序(Memory Order) 默认为顺序一致性(std::memory_order_seq_cst),如果需要指定其他的内存序,需使用 std::atomic::store()。
  2. 原子对象没有定义的复制赋值,但是注意它们是可以隐式地转换为类型T。

注意:不同于大多数赋值运算符,原子类型的赋值运算符不返回到其左侧参数的引用。它们会返回存储值的副本。

#include <iostream>
#include <atomic>
 
int main()
{
    std::atomic<int> a = 1;
    std::cout << a.load() << std::endl;

    a = 2;
    std::cout << a.load() << std::endl;

    std::atomic<int> b = a;  // error
}
复制代码

std::atomic::operator()

原子地加载并返回原子变量的当前值。等价于 load() 。

operator T() const volatile noexcept;
operator T() const noexcept;
复制代码

这是一个类型转换操作符:在一个表达式中计算一个原子对象,该表达式需要其包含类型(T)的值,调用这个成员函数,访问包含的值。此操作是原子操作,并使用顺序一致性(内存顺序顺序cst)。

示例:

// atomic::operator=/operator T example:
#include <iostream>       // std::cout
#include <atomic>         // std::atomic
#include <thread>         // std::thread, std::this_thread::yield

std::atomic<int> foo = 0;
std::atomic<int> bar = 0;

void set_foo(int x) {
  foo = x;
}
void copy_foo_to_bar () {
  while (foo==0) std::this_thread::yield();
  bar = static_cast<int>(foo);  // 返回foo的版本参数T的值
}
void print_bar() {
  while (bar==0) std::this_thread::yield();
  std::cout << "bar: " << bar << '\n';  // 10
}

int main ()
{
  std::thread first (print_bar);
  std::thread second (set_foo, 10);
  std::thread third (copy_foo_to_bar);
  first.join();
  second.join();
  third.join();
  return 0;
}
复制代码

std::atomic::is_lock_free

检查此类型所有对象上的原子操作是否免锁。若此类型所有对象上的原子操作免锁则为 true ,否则为 false 。

注意:std::atomic_flag 以外的所有原子类型可用互斥或其他锁定操作实现,而不一定用免锁的原子 CPU 指令。亦允许原子类型有时免锁,例如若给定架构上仅对齐的内存访问是天然原子的,则同类型的错误对齐对象必须用锁。

C++ 标准推荐(但不要求)免锁操作亦为免地址,即适用于使用共享内存的进程间交流。

bool is_lock_free() const volatile noexcept;
bool is_lock_free() const noexcept;
复制代码

示例:

#include <iostream>
#include <utility>
#include <atomic>
 
struct A { int a[100]; };
struct B { int x, y; };
int main()
{
    std::cout << std::boolalpha
              << "std::atomic<A> is lock free? "
              << std::atomic<A>{}.is_lock_free() << '\n'
              << "std::atomic<B> is lock free? "
              << std::atomic<B>{}.is_lock_free() << '\n';
}
复制代码

可能的输出:

std::atomic<A> is lock free? false
std::atomic<B> is lock free? true
复制代码

std::atomic::store

原子地以 val 替换当前值。按照 order 的值影响内存。order 必须是 std::memory_order_relaxedstd::memory_order_releasestd::memory_order_seq_cst 之一。否则行为未定义。

void store (T val, memory_order sync = memory_order_seq_cst) volatile noexcept;
void store (T val, memory_order sync = memory_order_seq_cst) noexcept;
复制代码

std::atomic::load

原子地加载并返回原子变量的当前值。按照 order 的值影响内存。order 必须是以下之一。否则行为未定义。

T load (memory_order sync = memory_order_seq_cst) const volatile noexcept;
T load (memory_order sync = memory_order_seq_cst) const noexcept;
复制代码

下面是一个atomic::load/store相关的例子:

// atomic::load/store example
#include <iostream>       // std::cout
#include <atomic>         // std::atomic, std::memory_order_relaxed
#include <thread>         // std::thread

std::atomic<int> foo (0);

void set_foo(int x) {
  foo.store(x, std::memory_order_relaxed);     // set value atomically
}

void print_foo() {
  int x;
  do {
    x = foo.load(std::memory_order_relaxed);  // get value atomically
  } while (x==0);
  std::cout << "foo: " << x << '\n';  // 10
}

int main ()
{
  std::thread first (print_foo);
  std::thread second (set_foo, 10);
  first.join();
  second.join();
  return 0;
}
复制代码

std::atomic::exchange

原子地替换原子对象的值并返回它先前持有的值,操作为读-修改-写操作。根据 order 的值影响内存。

T exchange (T val, memory_order sync = memory_order_seq_cst) volatile noexcept;
T exchange (T val, memory_order sync = memory_order_seq_cst) noexcept;
复制代码

示例:

// atomic::exchange example
#include <iostream>       // std::cout
#include <atomic>         // std::atomic
#include <thread>         // std::thread
#include <vector>         // std::vector

std::atomic<bool> ready (false);
std::atomic<bool> winner (false);

void count1m (int id) {
  while (!ready) {}                  // wait for the ready signal
  for (int i=0; i<1000000; ++i) {}   // go!, count to 1 million
  if (!winner.exchange(true)) { 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;
}
复制代码

atomic::compare_exchange_weak

(1)	
bool compare_exchange_weak (T& expected, T val,
           memory_order sync = memory_order_seq_cst) volatile noexcept;
bool compare_exchange_weak (T& expected, T val,
           memory_order sync = memory_order_seq_cst) noexcept;
(2)	
bool compare_exchange_weak (T& expected, T val,
           memory_order success, memory_order failure) volatile noexcept;
bool compare_exchange_weak (T& expected, T val,
           memory_order success, memory_order failure) noexcept;
复制代码

原子地比较 *thisexpected 的对象表示(对于一个T类型的对象,其对象表示 (object representation) 是和它开始于同一个地址,且长度为 sizeof(T) 的一段 unsigned char(或等价的 std::byte) (C++17 起)类型的对象序列),而若它们逐位相等,则以val替换前者(进行读修改写操作)。否则,将*this中的实际值加载进expected(进行加载操作)。

2)读修改写和加载操作的内存模型分别为 successfailure 。在 (1) 版本中, order 用于读修改写操作和加载操作,除了若 order == std::memory_order_acq_rel 或 order == std::memory_order_release ,则加载操作分别使用 std::memory_order_acquire 和 std::memory_order_relaxed。

参数

expected - 到期待在原子对象中找到的值的引用。若比较失败则被存储 *this 的实际值。
val - 若符合期待则存储于原子对象的值
success - 若比较成功,则读修改写操作所用的内存同步顺序。容许所有值。
failure - 若比较失败,则加载操作所用的内存同步顺序。不能为 std::memory_order_release 或 std::memory_order_acq_rel ,且不能指定强于 success 的顺序 (C++17 前)
sync - 两个操作所用的内存同步顺序

返回值

若成功更改底层原子值则为 true ,否则为 false 。

// atomic::compare_exchange_weak example:
#include <iostream>       // std::cout
#include <atomic>         // std::atomic
#include <thread>         // std::thread
#include <vector>         // std::vector

// a simple global linked list:
struct Node { int value; Node* next; };
std::atomic<Node*> list_head (nullptr);

void append (int val) {     // append an element to the list
  Node* oldHead = list_head;
  Node* newNode = new Node {val,oldHead};

  // what follows is equivalent to: list_head = newNode, but in a thread-safe way:
  while (!list_head.compare_exchange_weak(oldHead,newNode))
    newNode->next = oldHead;
}

int main ()
{
  // spawn 10 threads to fill the linked list:
  std::vector<std::thread> threads;
  for (int i=0; i<10; ++i) threads.push_back(std::thread(append,i));
  for (auto& th : threads) th.join();

  // print contents:
  for (Node* it = list_head; it!=nullptr; it=it->next)
    std::cout << ' ' << it->value;
  std::cout << '\n';

  // cleanup:
  Node* it; while (it=list_head) {list_head=it->next; delete it;}

  return 0;
}
复制代码

可能的输出:

 7 4 5 9 8 6 3 1 2 0
复制代码

atomic::compare_exchange_strong

(1)	
bool compare_exchange_strong (T& expected, T val,
           memory_order sync = memory_order_seq_cst) volatile noexcept;
bool compare_exchange_strong (T& expected, T val,
           memory_order sync = memory_order_seq_cst) noexcept;
(2)	
bool compare_exchange_strong (T& expected, T val,
           memory_order success, memory_order failure) volatile noexcept;
bool compare_exchange_strong (T& expected, T val,
           memory_order success, memory_order failure) noexcept;
复制代码

功能与weak版本一样,唯一的区别就是:弱版本允许(spuriously 地)返回 false(即原子对象所封装的值与参数 expected 的物理内容相同,但却仍然返回 false),即使在预期的实际情况与所包含的对象相比较时也是如此。对于某些循环算法来说,这可能是可接受的行为,并且可能会在某些平台上带来显著的性能提升。在这些虚假的失败中,函数返回false,而不修改预期。 对于非循环算法来说, compare_exchange_strong 通常是首选。

注意

比较和复制是逐位的(类似 std::memcmp 和 std::memcpy);不使用构造函数、赋值运算符或比较运算符。

允许函数的弱形式虚假地失败,即表现如同 *this != expected ,纵使它们相等。比较和交换在循环中时,弱版本在有的平台上会产出更好的性能。

弱版本比较和交换会要求循环,而强版本不要求时,推荐用强版本,除非 T 的对象表示可包含填充位、 (C++20 前)陷阱位或为同一值提供多个对象表示(例如浮点 NaN )。这些情况下,弱比较和交换典型地可用,因为它在一些稳定对象表示上快速收敛。

若有参与 union 某些成员,但非其他成员的值表示的位,则比较和交换可能始终失败,因为这种填充位在不参与活跃成员的值表示时拥有不确定值。

#include <atomic>
#include <iostream>
 
std::atomic<int>  ai;
 
int  tst_val= 4;
int  new_val= 5;
bool exchanged= false;
 
void valsout()
{
    std::cout << "ai= " << ai
	      << "  tst_val= " << tst_val
	      << "  new_val= " << new_val
	      << "  exchanged= " << std::boolalpha << exchanged
	      << "\n";
}
 
int main()
{
    ai= 3;
    valsout();
 
    // tst_val != ai   ==>  tst_val 被修改
    exchanged= ai.compare_exchange_strong( tst_val, new_val );
    valsout();
 
    // tst_val == ai   ==>  ai 被修改
    exchanged= ai.compare_exchange_strong( tst_val, new_val );
    valsout();
}
复制代码

输出:

ai= 3  tst_val= 4  new_val= 5  exchanged= false
ai= 3  tst_val= 3  new_val= 5  exchanged= false
ai= 5  tst_val= 3  new_val= 5  exchanged= true
复制代码

C风格API

针对C++的atomic提案,C也有一份对应提案,它应该提供相同语义但是(当然)不使用诸如template、reference和member function等C++特性。整个atomic接口有一个C-style对等品,称为C standard的一份扩充。 可以声明atomic_bool取代atomic,并替换store()和load,改用global函数,后者接受一个pointer指向对象,看下面例子:

#include <iostream>
#include <atomic>

std::atomic_bool ab;

int main()
{

    std::atomic_init(&ab, false);
    std::cout << "Result:" << ab << '\n';

    std::atomic_store(&ab, true);
    std::cout << "Result:" << ab << '\n';

    if(std::atomic_load(&ab)) {
        std::cout << "Result:" << ab << '\n';
    }
}
复制代码

C另有一个接口,采用_Atomic和_Atomic(),因此C-style接口一般只用于“需要在C和C++之间保持兼容”的代码身上。再详细的C风格的API,这里就不一一介绍了,基本都跟成员函数的功能保持一致,可以简单理解为如下这种实现:

// std::atomic_flag
void atomic_flag_xxx(std::atomic_flag* p) 
{
    p->xxx();
}

// std::atomic<T>
template< class T >
T atomic_xxx( std::atomic<T>* obj, typename std::atomic<T>::value_type desr ) {
    obj->xxx(desr);
}
复制代码

C风格数据类型

然而在C++中使用C-style atomic类型并不罕见,为了兼容新的C标准(C11),C++支持定义原子整型类型。这些类型都与std::atimic<T>特化类相对应,或是用同一接口特化的一个基本类型。 下图列出了最重要的atomic类型名称,除此之外还有更多,适用于较不常见的类型:

std::atomic_itype 原子类型 std::atomic<> 相关特化类
atomic_char std::atomic< char>
atomic_schar std::atomic< signed char>
atomic_uchar std::atomic< unsigned char>
atomic_int std::atomic< int>
atomic_uint std::atomic< unsigned>
atomic_short std::atomic< short>
atomic_ushort std::atomic< unsigned short>
atomic_long std::atomic< long>
atomic_ulong std::atomic< unsigned long>
atomic_llong std::atomic< long long>
atomic_ullong std::atomic< unsigned long long>
atomic_wchar_t std::atomic< wchar_t>
atomic_char16_t std::atomic< char16_t>
atomic_char32_t std::atomic< char32_t>

总结

atomic类型原子操作宣告C++11来到了多线程和并行编程的时代。相对于偏于底层的pthread库,C++通过定义原子类型的方式,轻松地化解了互斥访问共享数据的难题。不过C++也延续了其易于学习难于精通的特性,虽然atomic原子类型使用上较为简单,但其函数接口(原子操作)却可以有不用的内存顺序。C++11从各种不同的平台上抽象出了一个软件的内存模型,并以内存顺序进行描述,以使得想进一步挖掘并行系统性能的程序员有足够简单的手段来完成以往只能通过内联汇编来完成的工作。

本文介绍的 std::atomic_flagstd::atomic 内容就这些啦,另外,在C++11 标准库中, std::atomic 提供了针对整形(integral)和指针类型的特化实现,下一篇我会给大家介绍 C++11 的标准库中std::atomic 针对整形(integral)和指针类型的特化版本做了哪些改进。

参考

C++ Reference (cplusplus.com)

猜你喜欢

转载自juejin.im/post/7086226046931959838
今日推荐