使用omp_get_thread_num索引全局vector是否安全?多线程调用会出错吗
结论
这段代码在多个线程并发调用computeThings()时一定会出现运行异常,核心原因是omp_get_thread_num()的返回值不存在和原生线程的全局一对一映射关系。
1. omp_get_thread_num()的实际作用域
OpenMP标准中对该接口的定义非常明确:
- 返回值是当前线程在它所属的parallel并行域(线程组)内的本地编号,取值范围为
0 ~ omp_get_num_threads() - 1 - 该编号仅在当前并行域的生命周期内有效,且仅在当前并行域内部唯一,没有全局唯一性
- 当多个独立的parallel并行域同时运行时(比如多个外层原生线程同时调用
computeThings(),各自触发了一个#pragma omp parallel for并行域),不同并行域内的线程会各自从0开始编号,会出现大量重复的编号值。
2. 代码的具体问题
你代码中定义的静态全局std::vector<CustomAllocator> vec是所有线程共享的,长度为全局最大OpenMP线程数。当并发调用computeThings()时:
- 不同并行域中编号相同的线程,会获取到
vec中同一个下标的CustomAllocator实例 - 多个线程同时无锁读写同一个
CustomAllocator实例,会直接触发数据竞争:内存分配逻辑互相冲突、resetArena()被重复调用,最终出现内存损坏、程序崩溃等未定义行为。
注:如果只有单个线程调用
computeThings(),同一时间只有一个并行域在运行,线程编号全局唯一,不会出现上述问题,这也是很多人测试时没发现bug的原因。
3. 简单的修正方案
你要实现每个原生线程绑定独有的分配器,不需要手动用vector+OpenMP线程编号做映射,直接把CustomAllocator实例本身定义为线程本地存储即可:
// 直接把分配器实例做成线程本地,每个原生线程独有一份,互不干扰 thread_local CustomAllocator ts_alloc; template<class E> inline E* allocateBuffer( size_t count ) { return (E*)ts_alloc.allocate( count * sizeof( E ), alignof( E ) ); } void computeThings() { #pragma omp parallel for for( int64_t i = 0; i < 100000; i++ ) { float* const buffer = allocateBuffer<float>( 1024 * 256 ); // ..some computations here ts_alloc.resetArena(); } }
这种实现不需要额外的辅助类,也不存在并发冲突问题,性能反而更高。
内容的提问来源于stack exchange,提问作者Soonts
相关产品推荐
相关产品推荐

