轉載來自: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() 來讀寫原子對象內部的原始數據。