预留空间的C++ vector是否线程安全?代码安全性咨询
关于C++ STL vector多线程读写的线程安全性问题
已知一般情况下C++ STL不保证读写操作的线程安全性。若vector不会扩容,且读写区域不重叠,情况会如何?下方代码是否存在问题?(确保数据永远不会超过v的预留长度)
class Foo { public: Foo(){ v.reserve(99999); startWriter(); startReader(); } void startWriter() { std::thread t([&](){ while (true) { if (auto message = bar.recieve(); !message.is_empty()) { v.push_back(message.value()); } } }); t.detach(); } void startReader() { std::thread t([&](){ int last_idx = 0; while (true) { int new_idx = v.size(); for (int i = last_idx; i < new_idx; ++i) { v2.push_back(v[i]); } last_idx = new_idx; sleep(3); } }); t.detach(); } private: vector<int> v; vector<int> v2; Bar bar; };
或者改用v = vector<int>(99999);并通过v[actual_size++] = message.value();的方式?还是这两种方式都不具备线程安全性?
两种实现的问题分析
第一种reserve+push_back的实现
即使提前reserve避免了扩容,这个代码依然存在严重的线程安全问题:
push_back操作除了写入新元素,还会修改vector内部的size成员变量。C++标准没有规定vector的size()读取或push_back修改size的过程是原子操作,writer线程修改size和reader线程读取v.size()属于无同步的并发读写,会导致数据竞争,触发未定义行为。- 即使reader线程读到了正确的size,在访问
v[i]时,可能writer线程正在写入该位置的元素,同样是无同步的读写并发,属于未定义行为。
第二种预分配数组+手动计数的实现
这种方式同样不具备线程安全性:
actual_size++不是原子操作,它包含读取、加1、写入三个步骤,多线程环境下会出现计数错误;即使是单writer线程,reader线程读取actual_size时没有同步机制,可能读到过期的旧值。- 当writer线程写入
v[actual_size]的同时,reader线程访问该位置,属于无同步的读写并发,依然是C++内存模型中的数据竞争,触发未定义行为。
正确的处理方式
只要是多线程对共享的vector(或其关联的计数变量)进行读写操作,必须引入同步机制:
- 使用
std::mutex保护所有对vector和计数变量的访问,确保同一时间只有一个线程操作共享资源。 - 用
std::atomic<int>来管理actual_size,并通过合适的std::memory_order内存序保证元素写入的可见性,避免reader读到未完全写入的元素。 - 直接使用现成的线程安全数据结构,比如实现生产者-消费者模型的队列,避免手动管理vector的并发问题。
内容的提问来源于stack exchange,提问作者Wayne Tseng
相关产品推荐
相关产品推荐

