为什么需要多个shared_future对象来同步数据

时间:2016-12-29 00:42:05

标签: c++ multithreading c++11 future

通过std::promisestd::shared_future与多个线程共享指向数据结构的指针。 从Anthony Williams(第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的副本:

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的副本,为什么这只能保证正常工作呢?

1 个答案:

答案 0 :(得分:3)

使用单个全局shared_future的实现完全正常,如果有点不寻常,那么这本书似乎有误。

  

[futures.shared_future]¶2

     

[注意: shared_future的成员函数不与自身同步,但它们与共享状态同步。 - 结束记录]

注释是非规范性的,因此上述内容冗余地明确了一个已经隐含在规范性措辞中的事实。

  

[intro.races]¶2

     

两个表达式评估冲突如果其中一个修改了内存位置而另一个读取或修改了相同的内存位置。

     

<强>¶6

     

某些库调用其他线程执行的其他库调用同步。

     

[...在与......同步的之前定义的其他段落<]>

     

<强>¶19

     

如果它们由不同的线程执行,则两个动作可能并发 ...如果程序包含两个可能同时发生冲突的动作,则程序的执行包含数据竞争,其中至少有一个不是原子的,也不会发生在另一个之前......

     

[res.on.data.races]¶3

     

C ++标准库函数不应直接或间接修改除当前线程以外的线程可访问的对象,除非通过函数的非const参数直接或间接访问对象,包括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();
    ...
}