转载来自:https://subingwen.cn/cpp/atomic/#2-2-%E5%8E%9F%E5%AD%90%E5%8F%98%E9%87%8F%E7%89%88%E6%9C%AC
C++11 提供了一个原子类型 std::atomic<T>,通过这个原子类型管理的内部变量就可以称之为原子变量,我们可以给原子类型指定任意的类型作为模板参数,因此原子变量也可以是任意的类型。
C++11 内置了整形的原子变量,这样就可以更方便的使用原子变量了。在多线程操作中,使用原子变量之后就不需要再使用互斥量来保护该变量了,用起来更简洁。因为对原子变量进行的操作只能是一个原子操作(atomic operation),原子操作指的是不会被线程调度机制打断的操作,这种操作一旦开始,就一直运行到结束,中间不会有任何的上下文切换。多线程同时访问共享资源造成数据混乱的原因就是因为 CPU 的上下文切换导致的,使用原子变量解决了这个问题,因此互斥锁的使用也就不再需要了。
1. atomic 类成员
类定义
// 定义于头文件 <atomic> template< class T > struct atomic;
通过定义可得知:在使用这个模板类的时候,一定要指定模板类型。
构造函数
// ① atomic() noexcept = default; // ② constexpr atomic( T desired ) noexcept; // ③ atomic( const atomic& ) = delete; 构造函数①:默认无参构造函数。 构造函数②:使用 desired 初始化原子变量的值。 构造函数③:使用 =delete 显示删除拷贝构造函数,不允许进行对象之间的拷贝
公共成员函数
原子类型在类内部重载了 = 操作符,并且不允许在类的外部使用 = 进行对象的拷贝。
T operator=( T desired ) noexcept; T operator=( T desired ) volatile noexcept; atomic& operator=( const atomic& ) = delete; atomic& operator=( const atomic& ) volatile = delete;
原子地以 desired 替换当前值。按照 order 的值影响内存。
void store( T desired, std::memory_order order = std::memory_order_seq_cst ) noexcept; void store( T desired, std::memory_order order = std::memory_order_seq_cst ) volatile noexcept;
desired:存储到原子变量中的值
order:强制的内存顺序
原子地加载并返回原子变量的当前值。按照 order 的值影响内存。直接访问原子对象也可以得到原子变量的当前值。
T load( std::memory_order order = std::memory_order_seq_cst ) const noexcept; T load( std::memory_order order = std::memory_order_seq_cst ) const volatile noexcept;
C++20 新增成员
在 C++20 版本中添加了新的功能函数,可以通过原子类型来阻塞线程,和条件变量中的等待 / 通知函数是一样的。
公共成员函数 说明
wait(C++20) 阻塞线程直至被提醒且原子值更改
notify_one(C++20) 通知(唤醒)至少一个在原子对象上阻塞的线程
notify_all(C++20) 通知(唤醒)所有在原子对象上阻塞的线程
类型别名
别名 原始类型定义
atomic_bool(C++11) std::atomic<bool>
atomic_char(C++11) std::atomic<char>
atomic_schar(C++11) std::atomic<signed char>
atomic_uchar(C++11) std::atomic<unsigned char>
atomic_short(C++11) std::atomic<short>
atomic_ushort(C++11) std::atomic<unsigned short>
atomic_int(C++11) std::atomic<int>
atomic_uint(C++11) std::atomic<unsigned int>
atomic_long(C++11) std::atomic<long>
atomic_ulong(C++11) std::atomic<unsigned long>
atomic_llong(C++11) std::atomic<long long>
atomic_ullong(C++11) std::atomic<unsigned long long>
atomic_char8_t(C++20) std::atomic<char8_t>
atomic_char16_t(C++11) std::atomic<char16_t>
atomic_char32_t(C++11) std::atomic<char32_t>
atomic_wchar_t(C++11) std::atomic<wchar_t>
atomic_int8_t(C++11)(可选) std::atomic<std::int8_t>
atomic_uint8_t(C++11)(可选) std::atomic<std::uint8_t>
atomic_int16_t(C++11)(可选) std::atomic<std::int16_t>
atomic_uint16_t(C++11)(可选) std::atomic<std::uint16_t>
atomic_int32_t(C++11)(可选) std::atomic<std::int32_t>
atomic_uint32_t(C++11)(可选) std::atomic<std::uint32_t>
atomic_int64_t(C++11)(可选) std::atomic<std::int64_t>
atomic_uint64_t(C++11)(可选) std::atomic<std::uint64_t>
atomic_int_least8_t(C++11) std::atomic<std::int_least8_t>
atomic_uint_least8_t(C++11) std::atomic<std::uint_least8_t>
atomic_int_least16_t(C++11) std::atomic<std::int_least16_t>
atomic_uint_least16_t(C++11) std::atomic<std::uint_least16_t>
atomic_int_least32_t(C++11) std::atomic<std::int_least32_t>
atomic_uint_least32_t(C++11) std::atomic<std::uint_least32_t>
atomic_int_least64_t(C++11) std::atomic<std::int_least64_t>
atomic_uint_least64_t(C++11) std::atomic<std::uint_least64_t>
atomic_int_fast8_t(C++11) std::atomic<std::int_fast8_t>
atomic_uint_fast8_t(C++11) std::atomic<std::uint_fast8_t>
atomic_int_fast16_t(C++11) std::atomic<std::int_fast16_t>
atomic_uint_fast16_t(C++11) std::atomic<std::uint_fast16_t>
atomic_int_fast32_t(C++11) std::atomic<std::int_fast32_t>
atomic_uint_fast32_t(C++11) std::atomic<std::uint_fast32_t>
atomic_int_fast64_t(C++11) std::atomic<std::int_fast64_t>
atomic_uint_fast64_t(C++11) std::atomic<std::uint_fast64_t>
atomic_intptr_t(C++11)(可选) std::atomic<std::intptr_t>
atomic_uintptr_t(C++11)(可选) std::atomic<std::uintptr_t>
atomic_size_t(C++11) std::atomic<std::size_t>
atomic_ptrdiff_t(C++11) std::atomic<std::ptrdiff_t>
atomic_intmax_t(C++11) std::atomic<std::intmax_t>
atomic_uintmax_t(C++11) std::atomic<std::uintmax_t>
2. 原子变量的使用
假设我们要制作一个多线程交替数数的计数器,我们使用互斥锁和原子变量的方式分别进行实现,对比一下二者的差异:
2.1 互斥锁版本
#include <iostream> #include <thread> #include <mutex> #include <atomic> #include <functional> using namespace std; struct Counter { void increment() { for (int i = 0; i < 10; ++i) { lock_guard<mutex> locker(m_mutex); m_value++; cout << "increment number: " << m_value << ", theadID: " << this_thread::get_id() << endl; this_thread::sleep_for(chrono::milliseconds(100)); } } void decrement() { for (int i = 0; i < 10; ++i) { lock_guard<mutex> locker(m_mutex); m_value--; cout << "decrement number: " << m_value << ", theadID: " << this_thread::get_id() << endl; this_thread::sleep_for(chrono::milliseconds(100)); } } int m_value = 0; mutex m_mutex; }; int main() { Counter c; auto increment = bind(&Counter::increment, &c); auto decrement = bind(&Counter::decrement, &c); thread t1(increment); thread t2(decrement); t1.join(); t2.join(); return 0; }
2.2 原子变量版本
#include <iostream> #include <thread> #include <atomic> #include <functional> using namespace std; struct Counter { void increment() { for (int i = 0; i < 10; ++i) { m_value++; cout << "increment number: " << m_value << ", theadID: " << this_thread::get_id() << endl; this_thread::sleep_for(chrono::milliseconds(500)); } } void decrement() { for (int i = 0; i < 10; ++i) { m_value--; cout << "decrement number: " << m_value << ", theadID: " << this_thread::get_id() << endl; this_thread::sleep_for(chrono::milliseconds(500)); } } // atomic<int> == atomic_int atoimc_int m_value = 0; }; int main() { Counter c; auto increment = bind(&Counter::increment, &c); auto decrement = bind(&Counter::decrement, &c); thread t1(increment); thread t2(decrement); t1.join(); t2.join(); return 0; }
通过代码的对比可以看出,使用了原子变量之后,就不需要再定义互斥量了,在使用上更加简便,并且这两种方式都能保证在多线程操作过程中数据的正确性,不会出现数据的混乱。
原子类型 atomic<T> 可以封装原始数据最终得到一个原子变量对象,操作原子对象能够得到和操作原始数据一样的效果,当然也可以通过 store() 和 load() 来读写原子对象内部的原始数据。