You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 06:35:31