使用Mutex仍出现C++并行程序持续数据竞争警告
解决OpenMP+Mutex并行程序中的ThreadSanitizer数据竞争警告
问题概述
使用OpenMP和Mutex实现共享vector同步,但ThreadSanitizer持续报告内存释放环节的数据竞争,警告指向operator delete函数。程序核心逻辑为文件读取+并行计算,问题出在共享vector的内存销毁阶段。
代码实现
#include <fstream> #include <sstream> #include <iostream> #include <vector> #include <cstring> #include <cmath> #include <omp.h> #include <mutex> void readTextPointList(int n, int d, const std::string &strFileName, std::vector<std::vector<double>> &result) { std::vector<std::vector<double>> PointList; std::string strLine; std::ifstream inFile(strFileName); if (inFile.is_open()) { int i = 0; while (getline(inFile, strLine) && i < n) { if (strLine.size() != 0) { std::stringstream sin(strLine); std::vector<double> values(d); for (int j = 0; j < d; j++) { double aa; sin >> aa; values[j] = aa; } PointList.push_back(values); i++; } } inFile.close(); // Use std::move to transfer ownership result = std::move(PointList); } else { std::cout << "Error: Unable to open file." << std::endl; } } std::mutex solveMutex; void solve(std::vector<std::vector<double>> &data, size_t width, size_t start) { std::vector<double> tuple(width); { std::lock_guard<std::mutex> lock(solveMutex); // Lock the critical section for (size_t i = 0; i < width; ++i) { tuple[i] = data[start][i]; } } // No need to unlock explicitly; std::lock_guard takes care of it when it goes out of scope } auto main(int argc, char **argv) -> int { if (argc < 2) { std::cout << "Usage: DIMENSIONALITY WINDOW" << std::endl; return 0; } size_t width = strtoul(argv[1], nullptr, 10); size_t datasize = strtoul(argv[2], nullptr, 10); // Read the data into a shared vector std::string filename = "mydata.txt"; std::vector<std::vector<double>> data; readTextPointList(datasize, width, filename, data); int core_number = 2; int step = datasize / core_number + 1; #pragma omp parallel for schedule(dynamic) for (int i = core_number - 1; i >= 0; i--) { solve(data, width, i * step); } return 0; }
编译命令
g++ -Wall -g -Wextra -fsanitize=thread -pedantic -std=c++20 -O3 -m64 -fopenmp main.cpp
ThreadSanitizer警告详情
================== WARNING: ThreadSanitizer: data race (pid=25822) Write of size 8 at 0x7b0800000060 by main thread: #0 operator delete(void*, unsigned long) ../../../../src/libsanitizer/tsan/tsan_new_delete.cpp:150 (libtsan.so.0+0x8e878) #1 __gnu_cxx::new_allocator<double>::deallocate(double*, unsigned long) /usr/include/c++/11/ext/new_allocator.h:145 (a.out+0x484c) #2 std::allocator<double>::deallocate(double*, unsigned long) /usr/include/c++/11/bits/allocator.h:199 (a.out+0x484c) #3 std::allocator_traits<std::allocator<double>>::deallocate(std::allocator<double>&, double*, unsigned long) /usr/include/c++/11/bits/alloc_traits.h:496 (a.out+0x484c) #4 std::_Vector_base<double, std::allocator<double>>::_M_deallocate(double*, unsigned long) /usr/include/c++/11/bits/stl_vector.h:354 (a.out+0x484c) #5 std::_Vector_base<double, std::allocator<double>>::~_Vector_base() /usr/include/c++/11/bits/stl_vector.h:335 (a.out+0x484c) #6 std::vector<double, std::allocator<double>>::~vector() /usr/include/c++/11/bits/stl_vector.h:683 (a.out+0x484c) #7 void std::destroy_at<std::vector<double, std::allocator<double>>>(std::vector<double, std::allocator<double>>*) /usr/include/c++/11/bits/stl_construct.h:88 (a.out+0x484c) #8 void std::_Destroy<std::vector<double, std::allocator<double>>>(std::vector<double, std::allocator<double>>*) /usr/include/c++/11/bits/stl_construct.h:149 (a.out+0x484c) #9 void std::_Destroy_aux<false>::__destroy<std::vector<double, std::allocator<double>>*>(std::vector<double, std::allocator<double>>*, std::vector<double, std::allocator<double>>*) /usr/include/c++/11/bits/stl_construct.h:163 (a.out+0x484c) #10 void std::_Destroy<std::vector<double, std::allocator<double>>*>(std::vector<double, std::allocator<double>>*, std::vector<double, std::allocator<double>>*) /usr/include/c++/11/bits/stl_construct.h:196 (a.out+0x484c) #11 void std::_Destroy<std::vector<double, std::allocator<double>>*, std::vector<double, std::allocator<double>>>(std::vector<double, std::allocator<double>>*, std::vector<double, std::allocator<double>>*, std::allocator<std::vector<double, std::allocator<double>>>&) /usr/include/c++/11/bits/alloc_traits.h:848 (a.out+0x484c) #12 std::vector<std::vector<double, std::allocator<double>>, std::allocator<std::vector<double, std::allocator<double>>>>::~vector() /usr/include/c++/11/bits/stl_vector.h:680 (a.out+0x484c) #13 main /home/walid/Desktop/minimise/build sanitize/1_________decl.cpp:90 (a.out+0x2c79) Previous read of size 8 at 0x7b0800000060 by thread T5 (mutexes: write M11): #0 solve(std::vector<std::vector<double, std::allocator<double>>, std::allocator<std::vector<double, std::allocator<double>>>>&, unsigned long, unsigned long) /home/walid/Desktop/minimise/build sanitize/1_________decl.cpp:56 (a.out+0x310b) #1 main._omp_fn.0 /home/walid/Desktop/minimise/build sanitize/1_________decl.cpp:86 (a.out+0x329d) #2 <null> <null> (libgomp.so.1+0x1dc0d) Mutex M11 (0x55bd20bbf160) created at: #0 pthread_mutex_lock ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:4240 (libtsan.so.0+0x53908) #1 __gthread_mutex_lock /usr/include/x86_64-linux-gnu/c++/11/bits/gthr-default.h:749 (a.out+0x30cf) #2 std::mutex::lock() /usr/include/c++/11/bits/std_mutex.h:100 (a.out+0x30cf) #3 std::lock_guard<std::mutex>::lock_guard(std::mutex&) /usr/include/c++/11/bits/std_mutex.h:229 (a.out+0x30cf) #4 solve(std::vector<std::vector<double, std::allocator<double>>, std::allocator<std::vector<double, std::allocator<double>>>>&, unsigned long, unsigned long) /home/walid/Desktop/minimise/build sanitize/1_________decl.cpp:53 (a.out+0x30cf) #5 main._omp_fn.0 /home/walid/Desktop/minimise/build sanitize/1_________decl.cpp:86 (a.out+0x329d) #6 <null> <null> (libgomp.so.1+0x1dc0d) Thread T5 (tid=25828, running) created by main thread at: #0 pthread_create ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:969 (libtsan.so.0+0x605b8) #1 <null> <null> (libgomp.so.1+0x1e25f) #2 __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58 (libc.so.6+0x29d8f) SUMMARY: ThreadSanitizer: data race ../../../../src/libsanitizer/tsan/tsan_new_delete.cpp:150 in operator delete(void*, unsigned long) ================== ================== WARNING: ThreadSanitizer: data race (pid=25822) Write of size 8 at 0x7b3000000000 by main thread: #0 operator delete(void*, unsigned long) ../../../../src/libsanitizer/tsan/tsan_new_delete.cpp:150 (libtsan.so.0+0x8e878) #1 __gnu_cxx::new_allocator<std::vector<double, std::allocator<double>>>::deallocate(std::vector<double, std::allocator<double>>*, unsigned long) /usr/include/c++/11/ext/new_allocator.h:145 (a.out+0x487a) #2 std::allocator<std::vector<double, std::allocator<double>>>::deallocate(std::vector<double, std::allocator<double>>*, unsigned long) /usr/include/c++/11/bits/allocator.h:199 (a.out+0x487a) #3 std::allocator_traits<std::allocator<std::vector<double, std::allocator<double>>>>::deallocate(std::allocator<std::vector<double, std::allocator<double>>>&, std::vector<double, std::allocator<double>>*, unsigned long) /usr/include/c++/11/bits/alloc_traits.h:496 (a.out+0x487a) #4 std::_Vector_base<std::vector<double, std::allocator<double>>, std::allocator<std::vector<double, std::allocator<double>>>>::_M_deallocate(std::vector<double, std::allocator<double>>*, unsigned long) /usr/include/c++/11/bits/stl_vector.h:354 (a.out+0x487a) #5 std::_Vector_base<std::vector<double, std::allocator<double>>, std::allocator<std::vector<double, std::allocator<double>>>>::~_Vector_base() /usr/include/c++/11/bits/stl_vector.h:335 (a.out+0x487a) #6 std::vector<std::vector<double, std::allocator<double>>, std::allocator<std::vector<double, std::allocator<double>>>>::~vector() /usr/include/c++/11/bits/stl_vector.h:683 (a.out+0x487a) #7 main /home/walid/Desktop/minimise/build sanitize/1_________decl.cpp:90 (a.out+0x2c79) Previous read of size 8 at 0x7b3000000000 by thread T5 (mutexes: write M11): #0 std::vector<double, std::allocator<double>>::operator[](unsigned long) /usr/include/c++/11/bits/stl_vector.h:1046 (a.out+0x30f6) #1 solve(std::vector<std::vector<double, std::allocator<double>>, std::allocator<std::vector<double, std::allocator<double>>>>&, unsigned long, unsigned long) /home/walid/Desktop/minimise/build sanitize/1_________decl.cpp:56 (a.out+0x30f6) #2 main._omp_fn.0 /home/walid/Desktop/minimise/build sanitize/1_________decl.cpp:86 (a.out+0x329d) #3 <null> <null> (libgomp.so.1+0x1dc0d) Mutex M11 (0x55bd20bbf160) created at: #0 pthread_mutex_lock ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:4240 (libtsan.so.0+0x53908) #1 __gthread_mutex_lock /usr/include/x86_64-linux-gnu/c++/11/bits/gthr-default.h:749 (a.out+0x30cf) #2 std::mutex::lock() /usr/include/c++/11/bits/std_mutex.h:100 (a.out+0x30cf) #3 std::lock_guard<std::mutex>::lock_guard(std::mutex&) /usr/include/c++/11/bits/std_mutex.h:229 (a.out+0x30cf) #4 solve(std::vector<std::vector<double, std::allocator<double>>, std::allocator<std::vector<double, std::allocator<double>>>>&, unsigned long, unsigned long) /home/walid/Desktop/minimise/build sanitize/1_________decl.cpp:53 (a.out+0x30cf) #5 main._omp_fn.0 /home/walid/Desktop/minimise/build sanitize/1_________decl.cpp:86 (a.out+0x329d) #6 <null> <null> (libgomp.so.1+0x1dc0d) Thread T5 (tid=25828, running) created by main thread at: #0 pthread_create ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:969 (libtsan.so.0+0x605b8) #1 <null> <null> (libgomp.so.1+0x1e25f) #2 __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58 (libc.so.6+0x29d8f) SUMMARY: ThreadSanitizer: data race ../../../../src/libsanitizer/tsan/tsan_new_delete.cpp:150 in operator delete(void*, unsigned long) ================== ThreadSanitizer: reported 2 warnings
警告原因
从栈信息可以明确:
- 主线程在
main函数退出时销毁局部变量data,触发外层vector和内部子vector的内存释放(对应operator delete调用)。 - 子线程T5此时仍在执行
solve函数,读取data[start][i]的内容,而该内存区域已被主线程标记为待释放,引发读写数据竞争。
额外问题:你对只读共享数据错误添加了互斥锁,虽然锁本身不会直接引发竞争,但不必要的同步会干扰线程调度,加剧优化场景下的生命周期冲突。
解决方案
方案1:移除不必要的互斥锁(最优)
data在并行区域中是只读的,多线程同时读取不会引发竞争,直接删除锁相关代码即可:
void solve(std::vector<std::vector<double>> &data, size_t width, size_t start) { std::vector<double> tuple(width); // 直接读取只读数据,无需锁 for (size_t i = 0; i < width; ++i) { tuple[i] = data[start][i]; } }
方案2:显式确保线程同步
若怀疑OpenMP隐式屏障失效,可添加显式屏障或手动管理内存生命周期:
#pragma omp parallel for schedule(dynamic) for (int i = core_number - 1; i >= 0; i--) { solve(data, width, i * step); } // 显式屏障,强制等待所有子线程完成 #pragma omp barrier // 或者用智能指针手动控制内存释放时机 auto data_ptr = std::make_unique<std::vector<std::vector<double>>>(); readTextPointList(datasize, width, filename, *data
相关产品推荐
相关产品推荐

