C++智能指针能否从严格意义上避免data race?相关疑问及场景咨询
C++智能指针与数据竞争的关系
核心结论
C++的智能指针(包括std::shared_ptr和std::unique_ptr)本身无法从严格意义上避免数据竞争。它们的设计核心是管理堆对象的生命周期,而非解决多线程对指向对象的访问同步问题。
针对两种智能指针的具体说明
std::shared_ptr:它的引用计数操作是原子性的,多线程间增减引用计数不会引发数据竞争,但这仅保护了指针自身的生命周期管理。如果多个shared_ptr指向同一个对象,多线程对该对象的读写操作(比如你提到的访问次数更新/读取)仍然会产生race condition,因为智能指针不会对指向对象的访问做同步限制。std::unique_ptr:它通过独占所有权机制确保同一时间只有一个unique_ptr指向对象,但这同样不代表指向对象的访问是线程安全的。如果通过裸指针、引用把对象地址传递给其他线程,或者在转移所有权时未做好同步,依然会出现数据竞争问题。
解决全局标志/共享数值的数据竞争方案
对于你提到的全局标志、堆上共享数值(如访问次数)这类场景,智能指针无法解决问题,需要借助专门的同步机制:
- 原子类型:对于布尔、整型这类简单数据,使用
std::atomic<T>(如std::atomic<int>、std::atomic<bool>)是最优选择。原子类型的读写操作本身就是线程安全的,无需额外加锁。 - 互斥锁:如果涉及复杂对象或多步操作的同步,使用
std::mutex配合std::lock_guard或std::unique_lock来包裹读写逻辑,确保同一时间只有一个线程访问共享资源。
示例场景的解决方案
针对你提到的"线程更新访问次数、线程定期读取日志"的场景,分别提供两种实现:
方案1:使用原子类型
#include <atomic> #include <thread> #include <iostream> #include <chrono> std::atomic<int> visit_count(0); // 服务访客的线程函数:原子自增访问次数 void serve_visitor() { for (int i = 0; i < 1000; ++i) { visit_count.fetch_add(1, std::memory_order_relaxed); std::this_thread::sleep_for(std::chrono::microseconds(10)); } } // 日志线程函数:原子读取当前访问次数 void log_visits() { for (int i = 0; i < 10; ++i) { int current = visit_count.load(std::memory_order_relaxed); std::cout << "当前访问次数:" << current << std::endl; std::this_thread::sleep_for(std::chrono::seconds(1)); } } int main() { std::thread t1(serve_visitor); std::thread t2(log_visits); t1.join(); t2.join(); return 0; }
方案2:使用互斥锁
#include <mutex> #include <thread> #include <iostream> #include <chrono> int visit_count = 0; std::mutex mtx; // 服务访客的线程函数:加锁后更新访问次数 void serve_visitor() { for (int i = 0; i < 1000; ++i) { std::lock_guard<std::mutex> lock(mtx); visit_count++; std::this_thread::sleep_for(std::chrono::microseconds(10)); } } // 日志线程函数:加锁后读取访问次数 void log_visits() { for (int i = 0; i < 10; ++i) { std::lock_guard<std::mutex> lock(mtx); int current = visit_count; std::cout << "当前访问次数:" << current << std::endl; std::this_thread::sleep_for(std::chrono::seconds(1)); } } int main() { std::thread t1(serve_visitor); std::thread t2(log_visits); t1.join(); t2.join(); return 0; }
内容的提问来源于stack exchange,提问作者George Y
相关产品推荐
相关产品推荐

