C++中使用OpenMP出现Segmentation Fault等异常的求助
OpenMP并行化后运行异常的解决建议
问题描述
在C++代码中使用#pragma omp parallel for并行处理循环后,程序运行结果不稳定:有时正常输出,有时触发Segmentation Fault,有时出现内存错误提示(如"Incorrect checksum for freed object...")。移除并行指令后程序运行正常。涉及代码如下:
#include <algorithm> #include <cstdint> #include <iomanip> #include <iostream> #include <numeric> #include <string> #include <vector> #include <boost/multiprecision/cpp_int.hpp> using namespace std; using boost::multiprecision::cpp_int; // generates prime numbers under n vector<int> generatePrime(int n) { vector<int> primes; for (int i = 2; i <= n; i++) { bool isPrime = true; for (int j = 0; j < primes.size(); j++) { if (i % primes[j] == 0) { isPrime = false; break; } } if (isPrime) { primes.push_back(i); } } return primes; } // checks if an integer is a prime number bool chkPrime(vector<int> vec, vector<int> ref) { for (int i = 0; i < vec.size(); i++) { if (find(ref.begin(), ref.end(), vec[i]) == ref.end()) { return false; } } return true; } int main() { vector<int> primes = generatePrime(100); vector<cpp_int> row(1, 1); int maxAlleles = 1000; vector<vector<int>> rowPrime; for (int alleles = 1; alleles <= maxAlleles; alleles++) { vector<cpp_int> row1 = row; row1.push_back(0); row1.push_back(0); vector<cpp_int> row2 = row1; vector<cpp_int> row3 = row1; vector<cpp_int> rowFinal; rotate(row2.begin(), row2.end() - 1, row2.end()); rotate(row3.begin(), row3.end() - 2, row3.end()); for (int i = 0; i < row1.size(); i++) { // making the next row of the trinomial triangle rowFinal.push_back(row1[i] + row2[i] + row3[i]); } row = rowFinal; #pragma omp parallel for // for each number in the row, we will make the number into a string and divide it by 2 letters // and put it into a vector (splitTwo), starting from the beginning of the string for (int num = 0; num < row.size(); num++) { string item = to_string(row[num]); vector<int> splitTwo; int i = 0; if (item.length() % 2 == 0) { while (i <= item.length() - 2) { splitTwo.push_back(stoi(item.substr(i, 2))); i += 2; } } else { if (item.length() > 2) { while (i <= item.length() - 3) { splitTwo.push_back(stoi(item.substr(i, 2))); i += 2; } } int last_letter = item[item.length() - 1] - '0'; splitTwo.push_back(last_letter); } // we are going to push back splitTwo in rowPrime if all items in splitTwo are prime numbers if (chkPrime(splitTwo, primes) == true) { splitTwo.push_back(alleles); splitTwo.push_back(num); rowPrime.push_back(splitTwo); } } } vector<int> sum; for (int k = 0; k < rowPrime.size(); k++) { sum.push_back( accumulate(begin(rowPrime[k]), end(rowPrime[k]) - 2, 0, plus<int>()); } int idx = distance(begin(sum), max_element(begin(sum), end(sum))); for (int &i : rowPrime[idx]) { cout << i << ' '; } cout << sum[idx] << ' ' << rowPrime.size(); return 0; }
错误根源
问题出在竞态条件(Race Condition):多个线程同时对共享容器rowPrime执行push_back操作。std::vector的push_back不是线程安全的——当容器需要扩容时,会重新分配内存、拷贝元素,并发执行这个过程会导致内存结构损坏,进而引发崩溃或内存错误。
解决方法
方法1:用临界区保护共享容器的写入操作
在rowPrime.push_back外层添加#pragma omp critical指令,确保同一时间只有一个线程能修改rowPrime:
// ... 原有代码 ... if (chkPrime(splitTwo, primes) == true) { splitTwo.push_back(alleles); splitTwo.push_back(num); #pragma omp critical { rowPrime.push_back(splitTwo); } } // ... 原有代码 ...
这种方法简单直接,但如果符合条件的splitTwo数量很多,临界区会成为性能瓶颈。
方法2:线程私有容器收集结果,最后合并
每个线程使用自己的私有容器存储结果,循环结束后再合并到共享容器,减少临界区的使用频率:
// ... 原有代码 ... #pragma omp parallel { // 每个线程创建私有容器 vector<vector<int>> localRowPrime; #pragma omp for for (int num = 0; num < row.size(); num++) { // ... 原有splitTwo处理逻辑 ... if (chkPrime(splitTwo, primes) == true) { splitTwo.push_back(alleles); splitTwo.push_back(num); localRowPrime.push_back(splitTwo); } } // 临界区合并结果 #pragma omp critical { rowPrime.insert(rowPrime.end(), localRowPrime.begin(), localRowPrime.end()); } } // ... 原有代码 ...
这种方法性能更优,因为只有合并时才会进入临界区,适合处理大数据量的场景。
额外优化:减少不必要的拷贝
当前chkPrime函数的参数是传值(vector<int> vec, vector<int> ref),每次调用都会拷贝整个vector,浪费内存和时间。改成传引用可以大幅提升性能:
bool chkPrime(const vector<int>& vec, const vector<int>& ref) { for (int num : vec) { if (find(ref.begin(), ref.end(), num) == ref.end()) { return false; } } return true; }
另外,还可以把primes转换成std::unordered_set,将查找时间从O(n)降到O(1),进一步优化chkPrime的效率:
// 在main函数中: unordered_set<int> primeSet(primes.begin(), primes.end()); // 修改chkPrime函数: bool chkPrime(const vector<int>& vec, const unordered_set<int>& ref) { for (int num : vec) { if (!ref.count(num)) { return false; } } return true; }
内容的提问来源于stack exchange,提问作者Neutron
相关产品推荐
相关产品推荐

