使用可变数量的 std::thread 的引用问题

Posted

技术标签:

【中文标题】使用可变数量的 std::thread 的引用问题【英文标题】:Problems with references using variable number of std::thread 【发布时间】:2017-07-13 20:13:49 【问题描述】:

我使用了 posix 线程 (pthread),但我正在尝试使用标准 C++11 线程(它很可能也是下面的 pthread)。我已经在这里和其他论坛上发表了几篇文章,但我无法弄清楚我的问题。如果有人可以点亮,我会很高兴。

问题是我试图将数据传递给线程,修改里面的数据,并且在抛出的线程完成(加入)后,主线程必须看到修改。问题是在某些线程中数据(int)没有正确读取,因此输出结果不正确。

我可以编写的复制问题的最少代码是:

#include <iostream>
#include <vector>
#include <thread>

using namespace std;

void threadCallbackInt(int const & x)

    int & y = const_cast<int &>(x);
    y += 10;
    std::cout<<"Inside Thread x = "<<y << " at position = " << &y <<std::endl;


int main(int argc, char* argv[])

    unsigned int nthreads = 3;
    vector<int> ints;
    vector<std::thread> threads(nthreads);
    threads.reserve(nthreads);
    for(unsigned int i = 0; i < nthreads; i++)
        ints.push_back(i);

        std::cout<<"In Main Thread : Before Thread Start x = "<<ints.at(i)<< " at position = " << &ints.at(i) << std::endl;
        threads[i] = std::thread(threadCallbackInt,std::ref(ints[i]));
    

    cout << "size = " << threads.size() << endl;

    for(unsigned int i = 0; i < nthreads; i++)
        threads[i].join();
    

    for(unsigned int i = 0; i < nthreads; i++)
        std::cout<<"In Main Thread : After Thread Start x = "<<ints[i]<< " at position = " << &ints[i] << std::endl;
    
    return 0;

在几个论坛上有几个类似的帖子,但我认为最能解决我问题的帖子是C++ 11 std::thread strange behavior

请注意,我使用方法reserve是为了避免引用失效和编号,但无济于事。

取决于执行,我得到不同的输出。这是输出之一:

在主线程中:在线程开始之前 x = 0 位置 = 0x21d7c40

在主线程中:在线程开始之前 x = 1 位置 = 0x21d81e4

在主线程中:在线程开始之前 x = 2 位置 = 0x21d7c48

内部线程 x = 10 位置 = 0x21d81e4

大小 = 3

内部线程 x = 12 位置 = 0x21d7c48

内部线程 x = 10 位置 = 0x21d7c40

在主线程中:在线程开始后 x = 10 位置 = 0x21d7c40

在主线程中:在线程开始后 x = 1 位置 = 0x21d7c44

在主线程中:在线程开始后 x = 12 位置 = 0x21d7c48

请注意,其中一个 x 仍未更新,即使在线程完成后也是如此。这是因为用于此的内存不是正确的(在线程之前是 0x21d81e4,在线程之后是 0x21d7c40)。如果你运行代码,你会看到结果会有所不同,并且任何时候引用不匹配,我都会得到错误的结果。

任何关于可能解决方案的意见都将受到欢迎。对不起,如果这篇文章是重复的(我找不到这个确切问题的实际答案)。但如果是,请注明原帖。

【问题讨论】:

【参考方案1】:

执行ints.push_back(i)后a[0]的地址发生变化。 在打印 'In Main Thread...' 之前添加 std::cout &lt;&lt; "ints.size()=" &lt;&lt; ints.size() &lt;&lt; " &amp;ints[0]=" &lt;&lt; &amp;ints[0] &lt;&lt; endl; ,输出如下(我正在使用 Visual Studio 2017 测试代码并添加 EnterCriticalSection/LeaveCriticalSection 以使输出不会混淆。)

ints.size()=1 &ints[0]=0095BA28
In Main Thread : Before Thread Start x = 0 at position = 0095BA28
Inside Thread x = 10 at position = 0095BA28
ints.size()=2 &ints[0]=0095BA38
In Main Thread : Before Thread Start x = 1 at position = 0095BA3C
Inside Thread x = 11 at position = 0095BA3C
ints.size()=3 &ints[0]=0095E940
In Main Thread : Before Thread Start x = 2 at position = 0095E948
Inside Thread x = 12 at position = 0095E948
size = 3
In Main Thread : After Thread Start x = 10 at position = 0095E940
In Main Thread : After Thread Start x = 11 at position = 0095E944
In Main Thread : After Thread Start x = 12 at position = 0095E948

vector 对象被调整大小,int[0] 指向新分配的对象。 因此,添加 ints.reserve(nthreads); 会使指向 ints 向量的指针得以保留。

ints.size()=1 &ints[0]=00DD8608
In Main Thread : Before Thread Start x = 0 at position = 00DD8608
Inside Thread x = 10 at position = 00DD8608
ints.size()=2 &ints[0]=00DD8608
In Main Thread : Before Thread Start x = 1 at position = 00DD860C
Inside Thread x = 11 at position = 00DD860C
ints.size()=3 &ints[0]=00DD8608
In Main Thread : Before Thread Start x = 2 at position = 00DD8610
Inside Thread x = 12 at position = 00DD8610
size = 3
In Main Thread : After Thread Start x = 10 at position = 00DD8608
In Main Thread : After Thread Start x = 11 at position = 00DD860C
In Main Thread : After Thread Start x = 12 at position = 00DD8610

【讨论】:

非常感谢老兄!!!现在它起作用了!你知道,我一直专注于线程向量的 reserve(),但我忘记了整数向量。非常感谢。【参考方案2】:

intsints.push_back(i); 调整大小时,引用std::ref(ints[i]) 无效。如果你想这样做,你还需要在ints 上使用reserve

在使用 gcc 进行测试时,

i == 0,然后ints.capacity() == 1i == 1,然后ints.capacity() == 2i == 2,然后ints.capacity() == 4

这表明确实发生了调整大小。

【讨论】:

"*this所代表的线程的完成与(1.10)对应的成功join()返回同步" - 标准保证线程所做的所有更改在@987654334时都是可见的@返回。 谢谢——我对在 C++ 中访问非互斥保护的非原子其他线程修改数据非常谨慎,因为生成 UB 非常容易。 非常感谢猎鹰!现在它工作得很好!我专注于线程向量并将 reserve() 放在该向量上,但我忘记了向量整数。非常感谢。

以上是关于使用可变数量的 std::thread 的引用问题的主要内容,如果未能解决你的问题,请参考以下文章

std::thread 按引用传递调用复制构造函数

std::thread 通过引用传递向量元素

在 C++11 中通过引用 std::thread 传递对象

使用 ASIO 和 std::thread 制作 C++11 应用程序时对“pthread_create”的未定义引用错误

C++ std::thread 无效使用 void 表达式

什么是 std::thread::hardware_concurrency 返回?