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

GCD(C++)并行循环计算错误:代码问题还是GCD本身问题?

GCD并行累加结果错误的原因分析

问题背景与代码实现

以下代码读取TSV文件中的二维数值向量,执行多项式计算后累加得到结果,分别采用串行for循环与GCD的dispatch_apply并行循环实现:

TSV示例数据:

0.335832971253701   0.111283951925475
0.28470219633399    0.237865566116303
0.936298227948222   0.00759627336105169
0.441882043347137   0.744594979690941
0.811153553271307   0.906033395660231

C++实现代码:

#include <iostream>
#include <fstream>
#include <vector>
#include <cmath>
#include <sstream>
#include <dispatch/dispatch.h>

void load_problem(const std::string file, std::vector<std::pair<double,double>>& repo) {
    repo.clear();
    std::ifstream ifs(file);
    if (ifs.is_open()) {
        std::string line;
        while(getline(ifs, line)) {
            double x= std::nan("");
            double y= std::nan("");
            std::istringstream istr(line);
            istr >> std::skipws >> x >> y;
            if (!isnan(x) && !isnan(y)) {
                repo.push_back({x, y});
            };
        }
        ifs.close();
    }
  }

double do_work(const std::vector<std::pair<double,double>>& problem,const std::vector<double>& args, const size_t psz, size_t i) {
    double score = 0.0;
    for (__block size_t j=0; j < psz; j++) {
        score += args[j]*pow(problem[i].first,psz - i);
    }
    score += args[psz];
    return score;
}

int main() {
    // n-factor polynomial - test against a given problem 
    // provided as a set of tab-delimited x y values in 2d.txt
    std::vector<std::pair<double,double>> problem;
    const std::vector<double> args = {0.653398943958799,0.575258222088993,-5.54870756928019,-3.56273265353563,12.4189944179562,1.53213505629763,-4.09124685229838,5.7925805708932};

    load_problem("2d.tsv",problem); // tab-delimited doubles representing x, y.
    
    const size_t psz = args.size() - 1;

    __block double gcd_accumulator = 0.0;
    dispatch_apply(problem.size(), dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^(size_t i){
        gcd_accumulator += do_work(problem, args, psz, i);
    });
        
    double for_accumulator = 0.0;
    for (size_t i=0; i < problem.size(); i++) {
        for_accumulator += do_work(problem, args, psz, i);
    }
    std::cout << gcd_accumulator << std::endl;
    std::cout << for_accumulator << std::endl;

}

测试现象

  • 数据量较小时(如5行),两种方式计算结果一致:
31.5181
31.5181
  • 数据量增至2000行时,GCD并行计算结果始终错误:
9701.44
11589.8

仅修改TSV文件的条目数量就会触发该问题。

调试尝试

  1. 尝试用std::atomic解决并发问题,因直接捕获原子变量会触发拷贝构造函数删除错误,改用指针实现:
std::atomic<double> gcd_a = 0;
__block std::atomic<double>* gcd_accumulator = &gcd_a;
dispatch_apply(problem.size(), dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^(size_t i){
    *gcd_accumulator = *gcd_accumulator + do_work(problem, args, psz, i);
});

但结果仍错误:

3476.98 <--- GCD结果
11589.8 <--- 正确串行结果
  1. 改用先将并行计算结果存入vector再串行累加,结果完全正确:
__block std::vector<double> answers;
answers.reserve(problem.size());

dispatch_apply(problem.size(), dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^(size_t i){
    answers[i] = do_work(problem, args, psz, i);
});
double gcd_accumulator = 0;
for (size_t i=0; i < problem.size(); i++) {
    gcd_accumulator += answers[i];
}

问题咨询

该错误的根源是代码实现问题,还是Grand Central Dispatch本身的问题?


解答

这完全是代码实现问题,和GCD本身没有关系,具体原因如下:

  1. 初始并行累加的线程安全问题
    gcd_accumulator += value本质是三步操作:读取当前值、计算新值、写入新值。在多线程环境下,多个线程可能同时读取到相同的旧值,计算后覆盖写入,导致部分累加操作被丢失——小数据量时并发线程少,冲突概率低,所以偶尔结果正确;数据量增大后并发线程增多,冲突概率飙升,结果就会明显错误。

  2. std::atomic的误用
    你用*gcd_accumulator = *gcd_accumulator + value的写法并没有实现原子累加:读取原子变量的值、计算和、再赋值回去,这三步仍然是分开的非原子操作,线程之间依然会出现竞态条件。正确的原子累加应该使用std::atomic的fetch_add方法,它能保证整个累加操作的原子性:

std::atomic<double> gcd_accumulator = 0.0;
dispatch_apply(problem.size(), dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^(size_t i){
    gcd_accumulator.fetch_add(do_work(problem, args, psz, i), std::memory_order_relaxed);
});

(注:std::memory_order_relaxed适合这种纯累加的场景,性能最优)

  1. 先存vector再累加的正确性
    这种方式每个线程只写入vector中属于自己的索引位置,不存在多线程写同一内存地址的情况,完全避免了竞态条件,最后串行累加自然能得到正确结果,这是一种典型的"分治"思路——并行计算各个子任务,最后合并结果。

内容的提问来源于stack exchange,提问作者Konchog

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 12:40:27