boost:atomic 川长思鸟来 2022-10-13 12:50 287阅读 0赞 # 使用 # ## 引用库 ## atomic库需要编译才能使用: > * 在jamfile里指定lib的语句是:`lib boost_atomic`。 > * 在cmakelist中:`target_link_libraries(${PROJECT_NAME} boost_atomic )` ## 头文件 ## #include <boost/atomic.hpp> using namespace boost; # 类摘要 # atomic库定义了基本模板类atomic< T >,简化的类摘要如下: ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poaXpoZW5nZ3Vhbg_size_16_color_FFFFFF_t_70] atomic可以把对类型T的操作原子化,但不是任意的类型都可以原子化的,T可以是: * 标量类型,比如C++内建的算术类型、枚举、指针 * 只有平凡拷贝/转移构造、赋值和析构函数的类,并且可以使用memcmp执行比较操作----通常这样的类但是POD atomic< T >还针对整数类型和指针类型进行了特化,增加了一些特殊的操作(实际上特化的是atomic的基类base\_atomic,atomic使用模板元函数classify< T >计算T的类型,再从不同的特化形式base\_atomic继承): ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poaXpoZW5nZ3Vhbg_size_16_color_FFFFFF_t_70 1] 为了方便使用,atomic库还为这些原子化的整数和指针类型定义了typedef,名字都以atomic\_为前缀,比如: ![在这里插入图片描述][20210707100612937.png] # 使用 # ## 基本用法 ## 可以用两种方式创建atomic对象: * 有参数的构造函数创建有初值的atomic * 无参数的默认构造函数创建一个初值不确定的atomic----这种方式很危险,应当尽量避免(boost.atomic库不提供C++标准定义的C风格atomic\_init等函数,所以我们必须用有参数的构造函数来初始化atomic对象) atomic<int> a(10); // 初始化原子整数值为10 assert(a == 10); // 隐式类型转换 atomic<long> l; // 默认构造 std::cout << "atomic<long> l : "<< l ; // 初值不确定 atomic最重要的两个成员函数是store()和load(),它们以原子的方式存取atomic内部的值,不会因为并发访问导致数据不一致。atomic还使用操作符重载简化了store()和load()的调用方式:operator=等价于store(),而隐式类型转换operator T()则等价于load(): atomic<bool> a(false); // 原子化bool assert(!a.load()); // 显式调用load取值 a.store(true); // 显式调用store存值 assert(a); // 隐式类型转换,等价于load exchange()函数,顾名思义,它原子的“交换”两个值,在存值之后返回atomic内部原有的值 atomic<int> a(10); // 初始化原子整数值为10 assert(a.exchange(200) == 10); // 存值的同时返回原值 assert(a == 200); // 隐式类型转换,等价于load compare\_exchange\_weak()和compare\_exchange\_strong()是exchange()的增强棒球,也就是常说的CAS(compare-and-swap)操作。它们比较expected,如果相等则存值为desired,返回true/false表示原值是否被修改,但无论怎样最后在expected变量里都会输出原值。两者的区别是compare\_exchange\_weak()执行的速度快,但可能执行成功但却返回false: atomic<long> a(100); // 初始化原子整数值为100 long v = 100; if(a.compare_exchange_weak(v, 313)){ // 比较并交换:如果a == 100,则a和313交换 assert(a == 313 && v == 100); // 交换后,a变成了313,v还是100 } v = 200; auto b = a.compare_exchange_strong(v, 99); // 比较并交换:如果a == 200,那么a和v交换。因为a为313,所以不交换,a仍为313,v为200 assert(!b && v == 313); a.compare_exchange_weak(v, 99); //比较并交换:即使不满足(a == v)也交换 assert(a == 99 && v == 313); atomic的成员函数storage()可以直接获得atomic内部值的引用,能够以任意方式操作数据,但它也因此无法提供原子保证,在并发环境里应该尽量避免使用 ## 整数用法 ## 整数和bool类型是程序中最常用的数据类型,原子化后的atomic< T >和atomic< bool >的用法和原始类型几乎一样,可以安全的用在并发环境中,能够被多个CPU核心或者线程并发访问而无需特意使用保护手段,从而担当计数器或者标志位的角色. atomic< T>是一类特殊的atomic对象,除了具有基本的原子操作外还有整数特有的一些fetch\_xxx操作,它们执行对应的数学运送,然后返回原值而不是运算后的值。atomic< I >也重置了operator++、operator+=之类的操作符,这些操作符重载函数内部调用了fetch\_xxx函数,但返回运算后的值。 atomic<int> n(100); // 初始化原子整数值为100 assert(n.fetch_add(10) == 100); // 加法操作,返回原值 assert(n == 110); assert(++n == 111); // 前置++,返回运算后的值 assert(n++ == 111); // 后置++,返回原值 assert(n == 112); assert((n -= 10) == 102); //想防御fecth_sub,返回运算后的值 atomic<int> b(BOOST_BINARY(1101)); //二进制值1101 auto x = b.fetch_and(BOOST_BINARY(0110)); //逻辑与,返回原值 assert(x == BOOST_BINARY(1101) && b == BOOST_BINARY(0100)); assert((b |= BOOST_BINARY(1001)) // 相当于fetch_or == BOOST_BINARY(1101)); atomic< bool >比atomic< I >更特殊一些,它虽然也属于整数,但只有true/false两个取值,所以它没有fetch\_xxx操作,也没有操作符重载,接口与基本的atomic< T>相同、 ## 并发顺序的一致性 ## 在现代多CPU核心并发的环境里,编译器和CPU的优化都有可能打乱指令的执行顺序,虽然这可能会获得更高的执行效率,但也可能产生副作用,导致程序的流程不一定按照代码的顺序执行。 atomic库在头文件< boost/memory\_order.hpp>里定义了内存访问顺序概念,它是一个简单的枚举类型,允许用户自己控制代码的顺序一致性: ![在这里插入图片描述][20210707104553686.png] 实际上,atomic< T >的每个成员函数都有一个memory\_order默认参数(操作符重载函数除外),它指定了原子操作的内存顺序要求: ![在这里插入图片描述][20210707111257983.png] memory\_order参数的默认值是memory\_order\_seq\_cst,它是最严格的顺序一致性约束,不允许编译器或者CPU核心为优化而调整代码或者指令的执行顺序,保证在并发环境里任何CPU核心“看到”的指令顺序是相同的,程序的执行与单CPU单线程时相同,简化了程序员的工作。 如果memory\_order参数改用其他枚举值,那么在多核心并发执行时同一段代码可能会有不同的执行顺序,只有深刻理解了C++并发内存模型和顺序一致性概念才能确保写出正确的代码。 下面的代码使用atomic结合恰当的内存顺序,实现了一个高效的引用计数适配器ref\_count。 #include <iostream> #include <boost/atomic.hpp> using namespace boost; #include <boost/intrusive_ptr.hpp> template<typename T> class ref_count // 泛型的引用计数类 { private: typedef boost::atomic<int> atomic_type; // 定义atomic类型 mutable atomic_type m_count{ 0}; //初始化,注意是mutable protected: ref_count() { } // 这里不能使用default ~ref_count() { } public: typedef boost::intrusive_ptr<T> counted_ptr; //定义intrusive_ptr void add_ref() const // 增加引用计数 { m_count.fetch_add(1, boost::memory_order_relaxed); //不做任何顺序要求 } void sub_ref() const // 减少引用计数 { if (m_count.fetch_sub(1, boost::memory_order_release) == 1) //释放,值的修改后继操作可见 { boost::atomic_thread_fence( // 原子基本的线程防护 boost::memory_order_acquire); // 获取之前的修改 delete static_cast<const T*>(this); //删除指针,需要转型 } } decltype(m_count.load()) count() const //获取引用计数,注意decltype。C++14里面可以直接用auto推导取值 { return m_count.load(); } public: template<typename ... Args> //可变参数模板 static counted_ptr make_ptr(Args&& ... args) //工厂函数 { return counted_ptr(new T(std::forward<Args>(args)...)); } private: friend void intrusive_ptr_add_ref(const T* p) { p->add_ref(); } friend void intrusive_ptr_release(const T* p) { p->sub_ref(); } }; ref\_count的用法和intrusive\_ref\_counter一样,只要自定义类从它派生就会自动获得引用计数能力,能够被intrusive\_ptr管理。 class demo : public ref_count<demo>{ //添加引用计数能力 public: demo() = default; ~demo() = default; int x; }; int main (int argc, const char* argv[]) { auto p = demo::make_ptr(); p->x = 10; assert(p->x == 10); assert(p->count() == 1); auto k = p; k->x = 20; assert(p->x == 20); assert(p->count() == 2); return 0; } [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poaXpoZW5nZ3Vhbg_size_16_color_FFFFFF_t_70]: /images/20221005/78edba4a2a204f6ca4f03639bb955808.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poaXpoZW5nZ3Vhbg_size_16_color_FFFFFF_t_70 1]: /images/20221005/2db945e1ab1b4ced96a8b7a4ee506556.png [20210707100612937.png]: /images/20221005/eb7bdaa40a3c4571ab0c63d34692029e.png [20210707104553686.png]: /images/20221005/e92ecbd314dd4bc39dc76c8791a6bdaf.png [20210707111257983.png]: /images/20221005/660e97f9dac9453694ffe782c984d4fa.png
还没有评论,来说两句吧...