![](/img/trans.png)
[英]std::future or std::shared_future to wait for multiple threads
[英]why are multiple shared_future objects needed to synchronize data
指向数据结构的指针通过std::promise
和std::shared_future
与多个线程共享。 从安东尼·威廉姆斯(第85-86页)的《 C ++并发行动 》一书中,似乎只有当每个接收线程使用std::shared_future
对象的副本而不是每个线程访问单个对象时,才正确地同步数据。 ,全局std::shared_future
。
为了说明这一点,考虑一个创建大bigdata
并将一个指针传递给具有只读访问权限的多个线程的线程。 如果未正确处理线程之间的数据同步,则内存重新排序可能导致未定义的行为(例如, worker_thread
读取不完整的数据)。
此(不正确的?)实现使用单个全局std::shared_future
:
#include <future>
struct bigdata { ... };
std::shared_future<bigdata *> global_sf;
void worker_thread()
{
const bigdata *ptr = global_sf.get();
... // ptr read-only access
}
int main()
{
std::promise<bigdata *> pr;
global_sf = pr.get_future().share();
std::thread t1{worker_thread};
std::thread t2{worker_thread};
pr.set_value(new bigdata);
...
}
在这个(正确的)实现中,每个worker_thread
都获得一个std::shared_future
worker_thread
的副本:
void worker_thread(std::shared_future<bigdata *> sf)
{
const bigdata *ptr = sf.get();
...
}
int main()
{
std::promise<bigdata *> pr;
auto sf = pr.get_future().share();
std::thread t1{worker_thread, sf};
std::thread t2{worker_thread, sf};
pr.set_value(new bigdata);
....
我想知道为什么第一个版本不正确。
如果std::shared_future::get()
是一个非const成员函数,那将是有道理的,因为从多个线程访问单个std::shared_future
本身就是一场数据竞赛。 但是由于此成员函数被声明为const,并且global_sf
对象与线程同步,因此可以安全地从多个线程并行访问。
我的问题是,为什么只有每个worker_thread
接收到std::shared_future
worker_thread
的副本时,才能保证它能够正常工作?
您的使用单个全局shared_future
是完全可以的,即使有些不寻常,这本书也可能是错误的。
[futures.shared_future]¶2
[ 注意:
shared_future
成员函数不与自己同步,但与共享状态同步。 — 尾注 ]
注释是非规范性的,因此上述内容多余地使一个事实变得明确,该事实已在规范性措词中隐含。
[intro.races]¶2
如果两个表达式求值之一修改一个内存位置,而另一个表达式读取或修改了相同的内存位置,则这两个表达式求值会冲突 。
¶6
某些库调用与另一个线程执行的其他库调用同步 。
[...在与...同步之前 ,其他段落的定义发生了。]
¶19
如果两个动作是由不同的线程执行的,则这两个动作可能是并发的...如果程序包含两个潜在的并发冲突的动作,其中至少一个不是原子的,并且两个动作都不在另一个之前,则程序的执行包含数据争用 。 。
[res.on.data.races]¶3
C ++标准库函数不得直接或间接修改可由当前线程以外的线程访问的对象,除非通过该函数的非常量参数(包括
this
直接或间接访问这些对象。
因此,我们知道在不同线程中对global_sf.get()
调用可能是并发的,除非您为它们提供了额外的同步(例如互斥锁)。 但我们也知道,在不同线程中对global_sf.get()
调用不会冲突,因为它是const
方法,因此禁止修改可从多个线程(包括*this
访问的对象。 因此,不满足数据争用的定义(未排序,可能同时发生的冲突操作),程序不包含数据争用。
人们通常还是希望避免使用全局变量,但这是一个单独的问题。
请注意,如果这本书是正确的,则说明存在矛盾。 它声称是正确的代码仍然包含一个全局shared_future
,当它们创建本地副本时,可以从多个线程访问它们:
void worker_thread()
{
auto local_sf = global_sf; // <-- unsynchronized access of global_sf here
const bigdata *ptr = local_sf.get();
...
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.