如何以线程安全的方式使用std::ios_base::pword()?
问题分析与解决方案
原代码是否线程安全?
不安全,核心问题有两点:
- 数据竞争导致未定义行为:
b.pword(index)返回的void*是非原子类型,第一次读取p = b.pword(index)属于非原子读操作,若其他线程同时在锁内执行b.pword(index) = p(非原子写),两者构成数据竞争,这在C++标准中属于未定义行为。 - 内存屏障无效:你添加的
std::atomic_thread_fence依赖原子操作才能发挥同步作用,对非原子的指针读写完全起不到预期的内存可见性或原子性保证。第一次读取的p可能是部分写入的无效指针,导致空指针检查不可靠。
修正方案
根据C++版本不同,有三种可靠的实现方式:
方案1:使用C++20 std::atomic_ref(推荐,性能最优)
利用std::atomic_ref将非原子的void*转换为原子操作对象,确保所有对pword指针的读写都是原子的,同时保留双重检查锁的性能优势:
#include <atomic> #include <ios> #include <mutex> class my_obj { /* ... */ }; my_obj* get_obj_for( std::ios_base &b ) { static int const index = std::ios_base::xalloc(); // 将pword返回的void*&包装为原子引用 std::atomic_ref<void*> p_ref( b.pword( index ) ); void *p = p_ref.load( std::memory_order_acquire ); if ( p == nullptr ) { static std::mutex mutex; std::lock_guard<std::mutex> lock{ mutex }; p = p_ref.load( std::memory_order_acquire ); if ( p == nullptr ) { my_obj* new_obj = new my_obj; // 用release语义保证my_obj的初始化对其他线程可见 p_ref.store( new_obj, std::memory_order_release ); p = new_obj; } } return static_cast<my_obj*>( p ); }
方案2:兼容C++11/17的原子指针包装
如果无法使用C++20,可让pword指向一个std::atomic<my_obj*>,通过原子指针实现线程安全访问:
#include <atomic> #include <ios> #include <mutex> class my_obj { /* ... */ }; my_obj* get_obj_for( std::ios_base &b ) { static int const index = std::ios_base::xalloc(); void* stored_ptr = b.pword( index ); auto atomic_obj_ptr = static_cast<std::atomic<my_obj*>*>(stored_ptr); if (atomic_obj_ptr != nullptr) { return atomic_obj_ptr->load( std::memory_order_acquire ); } static std::mutex mtx; std::lock_guard<std::mutex> lock(mtx); stored_ptr = b.pword( index ); atomic_obj_ptr = static_cast<std::atomic<my_obj*>*>(stored_ptr); if (atomic_obj_ptr == nullptr) { atomic_obj_ptr = new std::atomic<my_obj*>(); my_obj* new_obj = new my_obj(); atomic_obj_ptr->store(new_obj, std::memory_order_release); b.pword(index) = atomic_obj_ptr; } return atomic_obj_ptr->load( std::memory_order_acquire ); }
方案3:简单锁保护(性能一般但实现简单)
如果对性能要求不高,直接用互斥锁保护所有对pword的访问,彻底避免数据竞争:
#include <ios> #include <mutex> class my_obj { /* ... */ }; my_obj* get_obj_for( std::ios_base &b ) { static int const index = std::ios_base::xalloc(); static std::mutex mtx; std::lock_guard<std::mutex> lock(mtx); void* p = b.pword(index); if (p == nullptr) { p = new my_obj; b.pword(index) = p; } return static_cast<my_obj*>(p); }
内容的提问来源于stack exchange,提问作者Paul J. Lucas
相关产品推荐
相关产品推荐

