C++线程争用条件模拟

C++ threads race condition simulation

本文关键字:模拟 争用条件 线程 C++      更新时间:2023-10-16

这是一个C++程序,它使用5不同的线程运行10次,每个线程递增countervalue,因此最终输出应该是500,这正是程序给出的输出。但是我不明白为什么每次输出应该不同时都会给出500,因为增量操作不是atomic并且没有使用锁,因此程序应该在每种情况下给出不同的输出。

编辑以增加竞争条件的可能性 我增加了循环计数,但仍然看不到任何变化的输出

#include <iostream>
#include <thread>
#include <vector>
struct Counter {
    int value;
    Counter() : value(0){}
    void increment(){
        value = value + 1000;
    }
};
int main(){
    int n = 50000;
    while(n--){
    Counter counter;
    std::vector<std::thread> threads;
    for(int i = 0; i < 5; ++i){
        threads.push_back(std::thread([&counter](){
            for(int i = 0; i < 1000; ++i){
                counter.increment();
            }
        }));
    }
    for(auto& thread : threads){
        thread.join();
    }
    std::cout << counter.value << std::endl;
    }
    return 0;
}

你只是很幸运:)

使用 clang++ 编译我的输出并不总是 500:

500
425
470
500
500
500
500
500
432
440

注意

将 g++ 与 -fsanitize=thread -static-libtsan:

WARNING: ThreadSanitizer: data race (pid=13871)
  Read of size 4 at 0x7ffd1037a9c0 by thread T2:
    #0 Counter::increment() <null> (Test+0x000000509c02)
    #1 main::{lambda()#1}::operator()() const <null> (Test+0x000000507ed1)
    #2 _M_invoke<> /usr/include/c++/5/functional:1531 (Test+0x0000005097d7)
    #3 operator() /usr/include/c++/5/functional:1520 (Test+0x0000005096b2)
    #4 _M_run /usr/include/c++/5/thread:115 (Test+0x0000005095ea)
    #5 <null> <null> (libstdc++.so.6+0x0000000b8c7f)
  Previous write of size 4 at 0x7ffd1037a9c0 by thread T1:
    #0 Counter::increment() <null> (Test+0x000000509c17)
    #1 main::{lambda()#1}::operator()() const <null> (Test+0x000000507ed1)
    #2 _M_invoke<> /usr/include/c++/5/functional:1531 (Test+0x0000005097d7)
    #3 operator() /usr/include/c++/5/functional:1520 (Test+0x0000005096b2)
    #4 _M_run /usr/include/c++/5/thread:115 (Test+0x0000005095ea)
    #5 <null> <null> (libstdc++.so.6+0x0000000b8c7f)

显示争用条件。(此外,在我的系统上,输出显示的结果不同于 500(。

g++

的选项在g++的文档中进行了解释(例如:man g++(。另请参阅:https://github.com/google/sanitizers/wiki#threadsanitizer。

仅仅因为您的代码具有竞争条件并不意味着它们会发生。这是他们最难的部分。很多时候,它们只发生在其他事情发生变化并且时间不同时。

这里有几个问题:增加到 100 可以非常快地完成。因此,在第二个线程启动之前,您的线程可能已经完成了一半。下一个线程等相同。所以你永远不知道你真的有 5 个并行。

您应该在每个线程的开头创建一个屏障,以确保它们同时启动。

也可以尝试比"100"多一点,只有 5 个线程。但这完全取决于系统/负载/时间。等。

为了增加竞争条件的概率,我增加了循环计数 但仍然看不到任何变化的输出

严格来说,您在此代码中有数据竞争,这是未定义的行为,因此您无法可靠地重现它。

但是你可以Counter重写为一些"等效"的代码,在increment中人为延迟:

struct Counter {
    int value;
    Counter() : value(0){}
    void increment(){
        int val=value;
        std::this_thread::sleep_for(std::chrono::milliseconds(1));
        ++val;
        value=val;
    }
};

我用这个计数器得到了以下输出,它远小于500

100
100
100
100
100
101
100
100
101
100