为什么这个多线程片段中存在竞争条件

why is there a race condition in this multithreading snippet

我在使用多线程的 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,主线程继续 运行ning 并递增 i,第二个线程是创建的 myid 将是 myid=1... 等等。因此打印输出将是 myID 增量 i/e 1,2,3

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

感谢您的帮助,谢谢!

I am wondering if there is a race condition for the i variable

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

and if so, how does it interleave?

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

I tried to compile it and I constantly got the Thread ID printout as 3, but I was surprised because I thought the variable had to be global in order to be accessed by the various new threads?

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

This is what I thought would happen: thread 1 is created, Fun starts to run in thread 1 with myid = 0, main thread continues running and increments i, 2nd thread is created and the myid for that would be myid=1... and so on. And so the printout would be the myID in increments i/e 1,2,3

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

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

#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 而导致的混乱输出,但我认为这不是本次讨论的一部分)。

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