传递对线程的引用时,std::ref 真的有必要吗?

Posted

技术标签:

【中文标题】传递对线程的引用时,std::ref 真的有必要吗?【英文标题】:Is std::ref really necessary when passing references to threads? 【发布时间】:2015-03-25 15:13:20 【问题描述】:

我正在阅读 C++ Concurrency in Action 并且在第 2 章中我被引导相信即使是函数原型,例如:

void MagicFunc(Data& myData);

打算像下面这样使用:

Data dataExample;
thread t(MagicFunc,dataExample);

我真的应该这样做

Data dataExample
thread t(MagicFunc,std::ref(dataExample));

否则,我期望对“dataExample”发生的更改不会发生。具体来说是这样的:

虽然 MagicFunc 期望传递第二个参数 参考,std::thread 构造函数 t 不知道;它的 忽略函数期望的参数类型和 盲目地复制提供的值。当它调用 Magicfunc 时,它会 最终传递对数据内部副本的引用而不是 引用数据本身。因此,当线程结束时, 这些更新将作为所提供的内部副本被丢弃 参数被销毁,并且 process_widget_data 将被传递 未更改的数据 myData 而不是正确更新的版本。

但是,使用以下程序对此进行测试

#include <iostream>
#include <thread>
#include <vector>
#include <chrono>
#include <assert.h>
using namespace std;
using namespace std::chrono;

const int NUM_VALS = 50000000;

#define _MULTICORE 

void AddValuesToSlots(vector<int>& vecVals,vector<int>::iterator& begin,
                      int num,int startNum)
    int i = startNum;
    auto end = begin + num;
    for (auto itr = begin; itr < end; ++itr)
        *itr = i++;
    


int main()

    vector<int> vecVals;
    vecVals.resize(NUM_VALS);

    //get number of cores and divide up the workload
    unsigned int numCores = thread::hardware_concurrency();
    unsigned int slotsPerThread = NUM_VALS / numCores;

    //for timing
    high_resolution_clock::time_point t1 = high_resolution_clock::now();


    thread* t = new thread[numCores];

    //get the iterator to the beginning
    auto begin = vecVals.begin();

#ifdef _MULTICORE
    for (int core = 0; core < numCores; ++core)
        t[core] = thread(AddValuesToSlots, vecVals, begin + core*slotsPerThread,
            slotsPerThread, core*slotsPerThread);
    

    for (int core = 0; core < numCores; ++core)
        t[core].join();
    
#else
    AddValuesToSlots(vecVals, begin, NUM_VALS, 0);
#endif


    delete[] t;

    //how long did it take?
    high_resolution_clock::time_point t2 = high_resolution_clock::now();
    cout << duration_cast<milliseconds>(t2-t1).count() << endl;

#ifdef _DEBUG
    //test that the values are correct
    for (int slot = 0; slot < NUM_VALS; ++slot)
        assert(vecVals[slot] == slot);
#endif

    return 0;

我已经尝试将vecVals 封装在std::ref 中,并且没有,两次执行都没有问题。那么std::ref 是否真的有必要并且提供的信息有误?

谢谢

【问题讨论】:

能否请您删除所有不相关的代码(您发布的大部分内容)并发布 MCVE? 像这样通过引用传递迭代器是不寻常的。 【参考方案1】:

您不会直接更改 vecVals。迭代器正在工作,因为复制迭代器是可以的,它仍然指向相同的内存地址

【讨论】:

现在看起来很明显。杰出的。谢谢!【参考方案2】:

根据标准,您发布的代码实际上是非法的。 std::thread 应该使用参数的右值副本调用 AddValuesToSlots

一些 C++ 编译器会出错,而是使用参数的左值副本调用它。

live example

测试编译器是否违反规则的简单方法是:

void func1(int&&)  std::cout << "func1\n"; 
void func2(int&)  std::cout << "func1\n"; 

int main() 
  int x;
  std::thread t1(func1, x);
  t1.join();
  std::thread t2(func2, x);
  t2.join();

如果t1 ctor 被接受而t2 被拒绝,则您的编译器是合规的。

如果t2 ctor 被接受而t1 被拒绝,则您的编译器违反了标准。

See here for more about this MSVC compiler bug

【讨论】:

t2 被接受而 t1 实际上被拒绝:错误 1 ​​错误 C2664: 'void (int &&)' : cannot convert argument 1 from 'int' to 'int &&' 所以它看起来像 Visual Studio在这两个方面都是有罪的-对于我的“非法”代码以及您发布的示例。你确定吗? @LloydCrawley 都被拒绝了?这太令人惊讶了!什么编译器、版本和标志? 对不起,我不小心提交得太早了,编辑了^ BTW Visual Studio Community 2013 vers。 12.0.31101.00 更新 4 @LloydCrawley 我知道 MSVC 搞错了:我很惊讶有一个编译器拒绝 both! MSVC 中的std C++11 库有许多这样的问题,而且(据我所知)它是封闭源代码,它们比开源库更难修复。这是实现中的一个微妙的怪癖:基本上因为您只会调用函数一次,将存储的参数包的内容移动到调用中是正确的做法(并且,作为标准要求的奖金)。我承认,这个需求有点迟钝。 @LloydCrawley ***.com/a/28330984/1774667 更多关于这个错误的讨论

以上是关于传递对线程的引用时,std::ref 真的有必要吗?的主要内容,如果未能解决你的问题,请参考以下文章

std::thread创建线程,使用std::ref()传递类对象参数

std::thread创建线程,使用std::ref()传递类对象参数

std::thread创建线程,使用std::ref()传递类对象参数

第7章 按值传递或按引用传递:7.3 使用std::ref()和std::cref()

thread库,附带程序介绍

为啥我的 std::ref 不能按预期工作?