【问题标题】:why is there a race condition in this multithreading snippet为什么这个多线程代码段中存在竞争条件
【发布时间】:2022-01-12 20:35:23
【问题描述】:

我在 c++ 中使用多线程编写了这段代码,但我不确定为什么会得到我得到的输出。

void Fun(int* var) {
    int myID;
    myID = *var;
    std::cout << "Thread ID: " << myID << std::endl;

}

int main()
{
    using ThreadVector = std::vector<std::thread>;
    ThreadVector tv;
    std::cout << std::thread::hardware_concurrency() << std::endl;
    for (int i = 0; i < 3  ; ++i)
    {
        auto th = std::thread(&Fun, &i);
        tv.push_back(std::move(th));
    }
    for (auto& elem : tv)
    {
        elem.join();
    }

}

我想知道 i 变量是否存在竞争条件,如果存在,它是如何交错的?我试图编译它,我不断得到线程 ID 打印输出为 3,但我很惊讶,因为我认为变量必须是全局的才能被各种新线程访问?

这就是我认为会发生的事情:线程 1 被创建,Fun 开始在线程 1 中运行,myid = 0,主线程继续运行并递增 i,创建第二个线程并且 myid 为 myid=1 ... 等等。因此打印输出将是 myID 以 i/e 1、2、3 为增量

我知道我可以用std::lock_guard 解决这个问题,但我只是想知道导致i 变量出现这种竞争条件的交错(加载、增量、存储)是如何发生的。

感谢您的帮助!

【问题讨论】:

  • myID = *var; 和 for 循环中的 ++i 之间存在竞争。 “Fun 开始在线程 1 中运行,myid = 0,主线程继续运行并递增 i” 没有理由认为它会按照这个顺序发生,这就是竞争条件。
  • 为什么不考虑循环结束后所有线程启动的可能性?最坏的情况是i 的作用域完成,线程访问终止i。只需按值传递i,而不是其地址&amp;i。
  • i 的线程和范围之间不是也有竞争吗?
  • Fun 开始在线程 1 中运行,myid = 0,主线程继续运行并递增 i,创建第二个线程并且 myid 为 myid=1... 等等on -- 单线程思维会导致多线程程序的消亡。

标签: c++ multithreading cpu


【解决方案1】:

我想知道 i 变量是否存在竞争条件

是的,绝对是。父线程写入i,这是一个非atomic 变量,子线程读取它,没有任何干预同步。这就是 C++ 中数据竞争的确切定义。

如果是这样,它是如何交错的?

C++ 中的数据竞争会导致未定义的行为,您可能观察到的任何行为都不必通过交错来解释。

我尝试编译它,但我经常将线程 ID 打印输出为 3,但我很惊讶,因为我认为该变量必须是全局变量才能被各种新线程访问?

不,它不必是全球性的。如果以某种方式将指针或对此类变量的引用传递给线程,则线程可以访问其他线程的本地变量。

这就是我认为会发生的事情:线程 1 被创建,Fun 开始在线程 1 中运行,myid = 0,主线程继续运行并递增 i,创建第二个线程并且 myid 为 myid=1 ... 等等。因此打印输出将是 myID 以 i/e 1、2、3 为增量

好吧,您的程序中根本没有任何东西会强制这些事件按该顺序发生(或变得可观察),因此实际上没有任何依据可以期望它们会发生。例如,完全有可能三个线程都启动了,但是直到main 中的循环完成之后才真正运行,此时i 的值为3。(或者更确切地说,i 曾经所在的内存,因为它现在超出了范围并且它的生命周期已经结束 - 这是一个单独的错误,你不能阻止这种情况的发生。)

【讨论】:

    【解决方案2】:

    这是不会出现数据竞争的代码版本:

    #include <iostream>
    #include <thread>
    #include <vector>
    
    // since `id` is passed by value, each thread will work on its own copy and no
    // data race is possible
    void fun(int id) { std::cout << "thread id: " << id << "\n"; }
    
    int main() {
      std::vector<std::thread> threads;
    
      for (auto id = 0; id < 3; ++id) {
        threads.emplace_back(fun, id);
      }
    
      for (auto& thread : threads) {
        thread.join();
      }
    }
    

    由于每个线程都接收到变量 id 的副本,因此不存在竞争(除了由于不同步 std::cout 导致的打乱输出,但我认为这不是本次讨论的一部分)。

    变量在多个线程中的使用不需要是全局的。事实上,全局变量常常使编写多线程代码变得更加困难,甚至几乎不可能,因为不能保证每次读取和写入都会适当地同步。

    【讨论】:

      猜你喜欢
      • 2019-12-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多