C++ Boost Atomic详细讲解
无水先生 人气:0一、说明
Boost.Atomic 提供类 boost::atomic,可用于创建原子变量。它们被称为原子变量,因为所有访问都是原子的。 Boost.Atomic 用于多线程程序,当在一个线程中访问变量不应被访问相同变量的另一个线程中断时。如果没有 boost::atomic,从多个线程访问共享变量的尝试将需要与锁同步。
boost::atomic 取决于支持原子变量访问的目标平台。否则,boost::atomic 使用锁。该库允许您检测目标平台是否支持原子变量访问。
如果您的开发环境支持 C++11,则不需要 Boost.Atomic。 C++11 标准库提供了一个头文件 atomic,它定义了与 Boost.Atomic 相同的功能。例如,您会发现一个名为 std::atomic 的类。
Boost.Atomic 支持与标准库大致相同的功能。虽然一些函数在 Boost.Atomic 中被重载,但它们在标准库中可能有不同的名称。标准库还提供了一些 Boost.Atomic 中缺少的函数,例如 std::atomic_init() 和 std::kill_dependency()。
二、示例和代码
示例 45.1。使用 boost::atomic
#include <boost/atomic.hpp> #include <thread> #include <iostream> boost::atomic<int> a{ 0 }; void thread() { ++a; } void thread_s() { std::cout << "Hello Thread" << a << '\n'; } int main() { std::thread t1{ thread }; std::thread t2{ thread }; std::thread t3{ thread_s }; std::thread t4{ thread_s }; t1.join(); t2.join(); t3.join(); t4.join(); std::cout << a << '\n'; }
示例 45.1 使用两个线程来递增 int 变量 a。该示例使用 boost::atomic 代替锁来对 a 进行原子访问。该示例将 2 写入标准输出。
boost::atomic 之所以有效,是因为一些处理器支持对变量的原子访问。如果增加一个 int 变量是一个原子操作,则不需要锁。如果此示例在无法将变量递增为原子操作的平台上运行,则 boost::atomic 使用锁。
示例 45.2。 boost::atomic 带锁或不带锁
#include <boost/atomic.hpp> #include <iostream> int main() { std::cout.setf(std::ios::boolalpha); boost::atomic<short> s; std::cout << s.is_lock_free() << '\n'; boost::atomic<int> i; std::cout << i.is_lock_free() << '\n'; boost::atomic<long> l; std::cout << l.is_lock_free() << '\n'; }
您可以在原子变量上调用 is_lock_free() 来检查是否在没有锁的情况下访问该变量。如果您在 Intel x86 处理器上运行示例,它会显示 true 三次。如果您在没有对 short、int 和 long 变量进行无锁访问的处理器上运行它,则会显示 false。
Boost.Atomic 提供了 BOOST_ATOMIC_INT_LOCK_FREE 和 BOOST_ATOMIC_LONG_LOCK_FREE 宏来在编译时检查哪些数据类型支持无锁访问。
示例 45.2 仅使用整型数据类型。您不应将 boost::atomic 与 std::string 或 std::vector 等类一起使用。 Boost.Atomic 支持整数、指针、布尔值 (bool) 和普通类。整数类型的示例包括 short、int 和 long。普通类定义可以使用 std::memcpy() 复制的对象。
示例 45.3。 boost::atomic 和 boost::memory_order_seq_cst
#include <boost/atomic.hpp> #include <thread> #include <iostream> boost::atomic<int> a{0}; void thread() { a.fetch_add(1, boost::memory_order_seq_cst); } int main() { std::thread t1{thread}; std::thread t2{thread}; t1.join(); t2.join(); std::cout << a << '\n'; }
示例 45.3 增加了两次——这次不是使用 operator++,而是调用 fetch_add()。成员函数 fetch_add() 可以采用两个参数:a 应该递增的数字和内存顺序。
内存顺序指定内存访问操作必须发生的顺序。默认情况下,这个顺序是不确定的,不依赖于代码行的顺序。只要程序表现得好像内存访问操作是按源代码顺序执行的,编译器和处理器就可以更改顺序。此规则仅适用于线程。如果使用多个线程,内存访问顺序的变化会导致程序运行错误。 Boost.Atomic 支持在访问变量时指定内存顺序,以确保内存访问在多线程程序中以所需的顺序发生。
注意
指定内存顺序可优化性能,但会增加复杂性并使编写正确代码变得更加困难。因此,在实践中,您应该有充分的理由使用内存顺序。
示例 45.3 使用内存顺序 boost::memory_order_seq_cst 将 a 递增 1。内存顺序代表顺序一致性。这是最严格的内存顺序。在 fetch_add() 调用之前出现的所有内存访问必须在执行此成员函数之前发生。在 fetch_add() 调用之后出现的所有内存访问都必须在执行此成员函数之后发生。编译器和处理器可以在调用 fetch_add() 之前和之后重新排序内存访问,但它们不得将内存访问从调用 fetch_add() 之前移动到调用之后,反之亦然。 boost::memory_order_seq_cst 是双向内存访问的严格边界。
加载全部内容