为什么添加线程不能带来进一步的性能提升

Sta*_*Lee 2 c++ concurrency multithreading stdatomic

我正在学习 C++ 多线程编程。我的测试程序很简单,功能就是统计一个原子变量从0到10000000(高一点也没关系)。我不明白为什么当我将线程数从8设置为16后,执行时间反而增加了一些而不是大幅下降。

std::atomic<int> atomicCounter{0};
void addcount(int threadId) {
    while(atomicCounter.load() < 10000000) {
        atomicCounter.fetch_add(1);
    }
}

void test() {
    const int maxNumThreads = 8;
    // Time::now() is to get the timestamp accurate to ns
    auto s_ts = Time::now();
    std::vector<std::thread> threads;
    for (int i = 0; i < maxNumThreads; i++) {
        threads.emplace_back([&]() {
            addcount(i);
        });
    }

    // join the threads
    for (auto& thread : threads) {
        thread.join();
    }
    threads.clear();
    auto e_ts = Time::now();
    LOG(INFO) << "Executing time : " << (e_ts - s_ts) / 1000 << " us";
}
Run Code Online (Sandbox Code Playgroud)

我通过更改在8线程和16线程下测试了几次maxNumThreads,平均执行时间分别为1.7ms和1.9ms。我的测试在配备 i9-12900k 处理器的机器上运行,该处理器有 16 个内核并支持 24 个并行超线程。

我真诚地感谢任何建议!


我想起了我的案例中明显的问题,因此我按如下方式修改了上面的代码,然后重新运行测试。结果符合预期(执行时间从 3.0ms 下降到 1.9ms)。感谢所有的回答和评论!

std::atomic<int> atomicCounter{0};
void addcount(int threadId) {
    while(atomicCounter.load() < 1000) {
        //simulate threads fetch jobs from a queue and execute them once a time
        atomicCounter.fetch_add(1);
        for(int i = 0; i < 10000000; i++);
    }
}

void test() {
    const int maxNumThreads = 8;
    // get the timestamp accurate to ns
    auto s_ts = Time::now();
    std::vector<std::thread> threads;
    for (int i = 0; i < maxNumThreads; i++) {
        threads.emplace_back([&]() {
            addcount(i);
        });
    }

    // Join the threads
    for (auto& thread : threads) {
        thread.join();
    }
    threads.clear();
    auto e_ts = Time::now();
    LOG(INFO) << "Executing time : " << (e_ts - s_ts) / 1000 << " us";
}
Run Code Online (Sandbox Code Playgroud)

Sol*_*low 5

您不能指望 16 个线程递增同一原子变量的速度比一个线程快。之所以不这样做是因为,无论有多少个线程,变量都只有一个,并且线程都必须轮流访问它。

想象一下教室里挤满了孩子,有一块白板和一辆装满弹珠的独轮车。目标是数弹珠。于是,老师在白板上画了一个方框,在方框里写下了数字“0”,并指导孩子们:

  • 排队,
  • 从独轮车上取出一块弹珠,
  • 增加框中的数字,然后
  • 走到队伍的后面,重复这个动作,直到手推车空了。

添加更多的孩子并不会让这个过程变得更快。我们需要的是一个更好的算法:

  • 每个孩子都有一个袋子,
  • 排队推独轮车,
  • 把你的包装满弹珠,
  • 把你的包带回你的桌子,数一下里面有多少颗弹珠,
  • 在白板前排队,
  • 将您在桌子上数的弹珠数量添加到框中的数量,
  • 如果不是空的,从手推车上再拿一个装满弹珠的袋子,然后重复。

第二种算法比第一种算法更复杂,但它为孩子们提供了更多时间并行工作,不是争夺独轮车和白板。每个孩子大部分时间都坐在自己的桌子上,数着自己装满弹珠的袋子,彼此之间根本没有互动。

在第二种情况下添加更多的孩子确实可以更快地完成工作。*


* 但回报是递减的。每个添加的孩子的价值都会比之前添加的孩子少一些。您可能会达到添加更多孩子没有意义的地步。这取决于他们在白板上完成工作的速度以及他们在办公桌上数弹珠的时间。请参阅https://en.m.wikipedia.org/wiki/Amdahl%27s_law