使用并行执行策略搭配std::back_inserter是否安全?
并行执行策略搭配std::back_insert_iterator的安全性分析
结论先行
- 你给出的示例代码写法存在明确的未定义行为,不具备线程安全性
- C++标准不会保证该场景下对目标向量
dst的插入操作是串行无竞争的
问题场景复现
std::vector<std::string> src(100, "test"), dst; std::move(std::execution::par_unseq, src.begin(), src.end(), std::back_inserter(dst));
规则依据
所有带并行执行策略的C++标准算法,对输出迭代器的核心约束为:算法执行过程中,所有对输出迭代器的写入操作不得产生数据竞争。也就是说,输出迭代器本身必须天然支持并发写入,算法实现不会为了适配非线程安全的输出操作,自动插入串行化、加锁等额外逻辑。
std::back_insert_iterator的写入逻辑,本质是直接调用绑定容器的push_back成员函数std::vector::push_back本身不具备多线程并发安全性:并发调用会同时触发内部扩容、元素搬移、结构指针偏移等操作,必然产生数据竞争
两类执行策略的具体风险
- 针对
std::execution::par:算法会将输入区间拆分为多个任务块分发到不同线程执行,每个线程独立调用输出迭代器写入对应处理结果,此时会直接出现多线程同时触发dst.push_back()的场景 - 针对
std::execution::par_unseq:除了多线程并行的基础风险外,还允许单线程内对迭代器操作做向量化乱序执行,对输出位置、写入顺序没有任何串行约束,风险等级更高 - 部分场景下该代码可能在小规模测试中看似运行正常,本质是并行实现的任务拆分粒度太粗,恰好没有触发并发写入,不代表该写法符合标准要求
常见正确实现方式
- 若需要并行移动元素到目标容器,可提前为目标容器预留足够空间:先执行
dst.resize(src.size()),直接使用dst.begin()作为输出迭代器,此时所有写入都是按固定索引的无冲突写入,不会产生数据竞争 - 若确实需要动态插入元素,可让每个并行任务块独立维护局部临时容器,所有并行任务执行完成后,再在单线程环境下将所有临时容器的内容按顺序移动到目标容器中
内容的提问来源于stack exchange,提问作者mentalmushroom
相关产品推荐
相关产品推荐

