使用std::execution::par_unseq执行策略时如何保证线程安全?
第二个版本不存在数据竞争,核心原因有两点:
1. 全局锁完全屏蔽了外部并发访问
incremental_all函数第一行的std::lock_guard<ProtectedY> guard(data);会直接获取ProtectedY内部的全局互斥锁,这把锁会在整个std::for_each执行期间全程持有。
按照ProtectedY的封装约定,所有需要访问内部std::vector<Y>的操作,都必须先持有这把锁,因此在for_each运行过程中,不可能有其他线程同时读写这个vector里的任何Y对象,完全杜绝了外部的并发冲突。
2. 算法内部的并发操作不存在共享内存访问
std::for_each的每个迭代处理的是vector里完全独立的不同Y实例,每个Y的data成员在内存上是完全分离的,没有任何跨迭代的共享可变数据。
哪怕par_unseq策略启用了多线程并行、SIMD向量化执行,不同的执行流/指令操作的都是不同的内存地址,完全不满足「多个线程同时访问同一块内存、且至少有一个是写操作」的数据竞争构成条件,因此不会产生并发问题。
补充说明:为什么par策略可以用单元素锁,par_unseq不行?
std::execution::par仅允许多线程并行,每个迭代会绑定到固定线程执行,不会出现同一个线程交错执行多个迭代的情况,每个元素的mutex只会被一个线程持有一次,既不会死锁,也能保证单个元素的并发安全。std::execution::par_unseq允许更激进的优化:包括向量化、同一个线程内交错执行多个迭代的指令、甚至将迭代在线程间迁移。而std::mutex是和持有线程绑定的,这种跨迭代的指令交错很容易导致同一个线程先后持有多个元素的mutex,引发死锁;同时SIMD向量化指令是单指令同时操作多个迭代的数据,根本无法匹配mutex加锁/解锁的执行逻辑,会直接触发未定义行为。
内容的提问来源于stack exchange,提问作者myoldgrandpa
相关产品推荐
相关产品推荐

