我正在学习C ++ 11的功能,并按照以下几行编写了一些代码
#include <vector>
#include <thread>
using std::thread;
using std::vector;
double Computation(int arg)
{
// some long-running computation
return 42.0;
}
double ConcurrentComputations()
{
const int N = 8; // number of threads
vector<thread> thr;
vector<double> res(N);
const int arg = 123456; // something or other
// Kick off threads which dump their results into res
for(int i=0; i<N; ++i)
thr.push_back(thread ([&res, i, arg]()
{ res[i] = Computation(arg); } ));
// Wait for them to finish and get results
double sum = 0;
for(int i=0; i<N; ++i) {
thr[i].join();
sum += res[i];
}
return sum;
}
在白天的寒冷中再次看到它,我认为我真的不应该在lambda函数中引用vector
并将数据转储到其中。我正在考虑将矢量作为常规数组并依赖于operator[]
的实现来简单地将i
添加到&res.front()
(也许我应该已经捕获&res.front()
而现在我在Stroustrup 4ed中进一步阅读,我可以看到我应该使用期货和承诺)。
然而,我的问题是,认为在实践中我可以逃脱我写的代码是否合理?
答案 0 :(得分:5)
你的代码实际上很好! (当混合线程时,默认情况下代码通常会被破坏,但在这种情况下不会。)
声明vector<double> res(N);
将为所有结果初始化具有足够空间的数组,因此向量将永远不会在循环中调整大小。
每个线程只写入向量的不同元素,并且线程构造函数和join()方法中存在隐式内存障碍,这样可以按照您的预期进行排序。
现在,关于这是否实际上是标准支持 - 嗯,可能不是(我发现关于std :: vector的线程安全的大多数引用只保证读取,而不是写入)。捕获前面也无济于事,因为你仍然在向矢量元素写入。
(请注意,我个人在平台上成功使用了这种确切的模式而没有遇到任何问题。)
答案 1 :(得分:3)
我相信你的代码(几乎)是合法的,行为定义明确(见下文对“几乎”部分的解释)。
标准的重要部分是:
17.6.5.9/5 C ++标准库函数不应通过其参数或其容器参数的元素访问可间接访问的对象,除非通过调用其规范所需的函数来容纳这些容器元素。 / p>
17.6.5.9/6 通过调用标准库容器或字符串成员函数获得的迭代器操作可以访问基础容器,但不得修改它。
res[i]
相当于*(res.begin() + i)
(23.2.3中的表101)这是唯一的障碍:我无法从标准的文本中证明res.begin()
没有修改矢量。如果我们假设一个合理的实现没有做到像这样严重的事情,那么剩下的就是顺利航行:在生成的迭代器上调用begin
后面跟operator+
和operator*
,两者都只能访问容器但不能修改容器。对共享对象的并发访问是可以的,它们不会导致数据竞争。
res[i]
返回double&
,然后执行对此double
对象的分配。这是一个修改 - 但没有两个线程修改同一个对象,所以这里也没有比赛。