当前位置: 首页 > 知识库问答 >
问题:

c++ - 求问这个C++的并发程序?

潘宸
2024-03-31
#include<bits/stdc++.h>using namespace std;std::atomic<int> counter(0);void increment() {    for (int i = 0; i < 100000; ++i) {        counter.store(counter.load() + 1);    }}int main() {    std::thread t1(increment);    std::thread t2(increment);    t1.join();    t2.join();    std::cout << "Counter: " << counter.load() << std::endl;    return 0;}

为啥最后打印类似100475,100425之类的值?
我看写这个的人说会打印200000.

共有2个答案

井通
2024-03-31

你先获取当前值,然后存入当前值+1,这之间如果有其它线程读取了 counter 的值,那么可能会重复操作。例如:

线程 1:load 345
线程 2: load 345
线程 2: store 346
线程 1: store 346

将代码修改为直接 +1 即可:

counter.fetch_add(1);
何升
2024-03-31

这段C++并发程序的目的是通过两个线程同时增加一个原子整数counter的值,每个线程增加100,000次。然而,你观察到最终打印的值并不总是200,000,而是类似于100,475或100,425之类的值。

这种情况的发生是由于线程调度和执行顺序的不确定性。尽管你创建了两个线程t1t2来同时增加counter,但操作系统线程调度器会在何时执行哪个线程之间进行切换是不确定的。

当你使用counter.store(counter.load() + 1)来增加counter的值时,这个过程实际上不是一个原子操作。即使std::atomic<int>保证了loadstore操作的原子性,但整个增加操作(读取、增加、存储)不是原子的。

具体来说,假设t1线程读取了counter的当前值(例如100,000),然后t2线程也被调度并执行,它也读取了counter的当前值(还是100,000),然后t1t2都将其值增加1并存储回去。在这种情况下,尽管每个线程都增加了1,但counter的最终值只增加了1,而不是2。

要修复这个问题并确保counter最终的值是200,000,你可以使用std::atomicfetch_add函数,它将增加和存储操作合并为一个原子操作:

void increment() {    for (int i = 0; i < 100000; ++i) {        counter.fetch_add(1);    }}

使用fetch_add函数,每次增加操作都会作为一个原子操作执行,从而确保即使在并发环境中,counter的值也会正确地增加。

 类似资料:
  • 作为对《C++ Concurrency in Action》的中文翻译。

  • 本书《C++ 并发编程指南》是个人在空余时间写的,由于时间仓促,加上自身水平有限,不可能写的很完善,也难免出现错误,如果你发现本书中的错误,或者有更好的想法, 欢迎给我反馈,我会第一时间给予答复。后续我会坚持完善这一系列的文章。也希望感兴趣的同学和我一起完成。 本书的创作出于以下两个目的: 传播知识,介绍 C++ 并发编程。目前国内还没有一本完整介绍 C++11 并发编程的中文书籍,希望本书可以帮

  • 通过多线程为C++并发提供标准化支持是件新鲜事。只有在C++11标准下,才能编写不依赖平台扩展的多线程代码。了解C++线程库中的众多规则前,先来了解一下其发展的历史。 1.3.1 C++多线程历史 C++98(1998)标准不承认线程的存在,并且各种语言要素的操作效果都以顺序抽象机的形式编写。不仅如此,内存模型也没有正式定义,所以在C++98标准下,没办法在缺少编译器相关扩展的情况下编写多线程应用

  • 求推荐一本C++的,多线程,并发有关的知识的书籍,名著之类的. 面试的时候似乎对这方面的知识的把握要求很高...我不知道背一个线程池的代码能不能管用.

  • 本文向大家介绍一个C#开发者重温C++的心路历程,包括了一个C#开发者重温C++的心路历程的使用技巧和注意事项,需要的朋友参考一下 前言 这是一篇C#开发重新学习C++的体验文章。 作为一个C#开发为什么要重新学习C++呢?因为在C#在很多业务场景需要调用一些C++编写的COM组件,如果不了解C++,那么,很容易。。。注定是要被C++同事忽悠的。 我在和很多C++开发者沟通的时候,发现他们都有一个

  • 队列并发两个不同的锁:一个用于 enqueue() 以保护同时排队的多个线程 如果队列已满,Add(enqueue)将跳过(返回)插入。如果队列为空,则删除(出列)将跳过删除。 我使用doRandon()生成了一堆0到1之间的随机数。我使用这些数字来决定是否添加/删除。 性能:我已尝试使用静态/动态线程分配测试队列。的执行时间