C++ pthread_once()中的争用条件?

C++ pthread_once()中的争用条件?,c++,c++11,pthreads,future,promise,C++,C++11,Pthreads,Future,Promise,我在一个线程中有一个std::future,它正在等待另一个线程中设置一个std::promise 编辑:使用示例应用程序更新了问题,该应用程序将永久阻止: 更新:如果我改用pthread\u屏障,下面的代码不会阻塞 我创建了一个测试应用程序,它说明了这一点: 基本上,类foo创建一个线程,该线程在其运行函数中设置一个promise,并在构造函数中等待设置该promise。一旦设置,它将增加原子计数 然后,我创建一组foo对象,将它们拆下,然后检查我的计数 #include <iostre

我在一个线程中有一个
std::future
,它正在等待另一个线程中设置一个
std::promise

编辑:使用示例应用程序更新了问题,该应用程序将永久阻止:

更新:如果我改用
pthread\u屏障
,下面的代码不会阻塞

我创建了一个测试应用程序,它说明了这一点:

基本上,类
foo
创建一个
线程
,该线程在其运行函数中设置一个
promise
,并在构造函数中等待设置该
promise
。一旦设置,它将增加原子计数

然后,我创建一组
foo
对象,将它们拆下,然后检查我的
计数

#include <iostream>
#include <thread>
#include <atomic>
#include <future>
#include <list>
#include <unistd.h>

struct foo
{
    foo(std::atomic<int>& count)
        : _stop(false)
    {
        std::promise<void> p;
        std::future <void> f = p.get_future();

        _thread = std::move(std::thread(std::bind(&foo::run, this, std::ref(p))));

        // block caller until my thread has started 
        f.wait();

        ++count; // my thread has started, increment the count
    }
    void run(std::promise<void>& p)
    {
        p.set_value(); // thread has started, wake up the future

        while (!_stop)
            sleep(1);
    }
    std::thread _thread;
    bool _stop;
};

int main(int argc, char* argv[])
{
    if (argc != 2)
    {
        std::cerr << "usage: " << argv[0] << " num_threads" << std::endl;
        return 1;
    }
    int num_threads = atoi(argv[1]);
    std::list<foo*> threads;
    std::atomic<int> count(0); // count will be inc'd once per thread

    std::cout << "creating threads" << std::endl;
    for (int i = 0; i < num_threads; ++i)
        threads.push_back(new foo(count));

    std::cout << "stopping threads" << std::endl;
    for (auto f : threads)
        f->_stop = true;

    std::cout << "joining threads" << std::endl;
    for (auto f : threads)
    {
        if (f->_thread.joinable())
            f->_thread.join();
    }

    std::cout << "count=" << count << (num_threads == count ? " pass" : " fail!") << std::endl;
    return (num_threads == count);
}
如果我现在
SIGABRT
该应用程序,则生成的堆栈跟踪显示它卡在
future::wait
堆栈跟踪如下所示:

// main thread
    pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    __gthread_cond_wait (__mutex=<optimized out>, __cond=<optimized out>) at libstdc++-v3/include/x86_64-unknown-linux-gnu/bits/gthr-default.h:846
    std::condition_variable::wait (this=<optimized out>, __lock=...) at ../../../../libstdc++-v3/src/condition_variable.cc:56
    std::condition_variable::wait<std::__future_base::_State_base::wait()::{lambda()#1}>(std::unique_lock<std::mutex>&, std::__future_base::_State_base::wait()::{lambda()#1}) (this=0x93a050, __lock=..., __p=...) at include/c++/4.7.0/condition_variable:93
    std::__future_base::_State_base::wait (this=0x93a018) at include/c++/4.7.0/future:331
    std::__basic_future<void>::wait (this=0x7fff32587870) at include/c++/4.7.0/future:576
    foo::foo (this=0x938320, count=...) at main.cpp:18
    main (argc=2, argv=0x7fff32587aa8) at main.cpp:52


// foo thread
    pthread_once () from /lib64/libpthread.so.0
    __gthread_once (__once=0x93a084, __func=0x4378a0 <__once_proxy@plt>) at gthr-default.h:718
    std::call_once<void (std::__future_base::_State_base::*)(std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> ()>&, bool&), std::__future_base::_State_base* const, std::reference_wrapper<std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> ()> >, std::reference_wrapper<bool> >(std::once_flag&, void (std::__future_base::_State_base::*&&)(std::function<std::unique_ptr<std::__future_base::_Result_base, ...) at include/c++/4.7.0/mutex:819
    std::promise<void>::set_value (this=0x7fff32587880) at include/c++/4.7.0/future:1206
    foo::run (this=0x938320, p=...) at main.cpp:26

尝试移动
std::promise p
这样,它就不是构造函数的本地成员,而是
struct foo

struct foo
{
    foo(std::atomic<int>& count)
        : _stop(false)
    {
        // std::promise<void> p;    // <-- moved to be a member
        std::future <void> f = p.get_future();

        // ...same as before...
    }
    void run(std::promise<void>& p)
    {
        // ... same ...
    }

    std::promise<void> p;   // <---
    std::thread _thread;
    bool _stop;
};
structfoo
{
foo(标准::原子和计数)
:_停止(错误)
{

//实际上,在本地
promise
对象的析构函数(在构造函数的末尾)和从线程调用
set\u value()
之间存在一个争用条件。也就是说,
set\u value()
唤醒主线程,该线程下一步将销毁promise对象,但是
set\u value()
功能尚未完成,并且死锁

阅读C++11标准,我不确定是否允许您使用:

void promise::set_value();

效果:以原子方式将值r存储在共享状态中,并使该状态就绪

但在其他地方:

set_value、set_exception、set_value_at_thread_exit和set_exception_at_thread_exit成员函数的行为就像在更新承诺对象时获取与承诺对象关联的单个互斥体一样

set\u value()
调用对于其他函数(如析构函数)是否应该是原子的

不,我想说不。这种效果相当于在其他线程仍在锁定互斥锁时销毁互斥锁。结果是未定义的

解决方案是使
p
比线程寿命长。我可以想到两种解决方案:

  • p
    成为班上的一员,就像迈克尔·伯尔在另一个答案中建议的那样

  • 将承诺移入线程

  • 在构造函数中:

    std::promise<void> p;
    std::future <void> f = p.get_future();
    _thread = std::thread(&foo::run, this, std::move(p));
    

    我认为这正是C++11定义两个不同类的原因:
    promise
    future
    :你将承诺移动到线程中,但你保留未来以恢复结果。

    我们可以看到
    发送承诺
    的更多内部内容吗?@anthony arnold,在操作体中添加了更多细节,当
    Wait::_p
    已初始化?我看不到它。@ildjarn,当Wait对象被初始化时,它将被默认构造created@ildjarn,参考文档,这让我相信默认构造已经足够了:但是在线程调用p.set_value()之前,代码并没有离开构造函数是的waiting@fork0:但一旦取消阻止,构造函数可能会在
    set\u value()
    返回之前完成。请注意,我不确定那里是否存在竞争(或者标准是否允许竞争),但如果
    set\u value
    在执行了任何信号后使用了
    p
    中的某个状态,它看起来可能会出现问题。此外,即使结果是这样,我也不确定这是否是
    set\u value
    实现中的某个错误ode>pthread
    API,或者这里的代码中使用了
    promise
    的错误。啊,没错。当然,实现可能是依赖的。可以说,一个implementation@fork0,我认为即使有一个正确的实现,也存在一场竞赛,请参阅我对标准的第二条评论要求
    promise::set_value()
    在更新
    promise
    对象(30.6.5/2)时保持一个互斥锁(或像保持互斥锁一样操作)。如果
    promise
    被销毁,而
    set_value
    promise
    对象中持有互斥锁,那么肯定会导致UB。因此,在
    promise::set_value()之后才销毁
    promise
    是不安全的
    返回,并且不再使用
    promise
    引用。我同意Jonathan的观点,即在
    promise::set_value()
    被销毁,而
    set_value()
    仍处于活动状态时,不需要实现才能使
    promise::set_value()
    安全。嗯,[futures.state]/9说要使状态准备就绪(在
    p.set_-value()
    )与
    f.wait()
    的返回同步,因此在设置值之前不应破坏承诺…但是更改代码以移动承诺似乎确实修复了错误。我必须检查
    promise::set_-value()
    …啊,但是
    p.set_-value()
    使状态就绪,然后唤醒所有等待的线程([futures.state]/6)。如果在构造函数开始等待之前状态就绪,则
    f.wait()
    立即返回,并且
    p
    被销毁,然后另一个线程在承诺被销毁后尝试唤醒被阻止的线程。因此我认为您已经正确地识别了问题。但是,正如堆栈跟踪所示,当它挂起时,构造函数仍在等待
    f.wait()
    所以这个承诺还没有被破坏……很奇怪。@Jonathan:我怀疑(实际上更像是胡思乱想)从未完成的
    f.wait()
    是在参与比赛的那场比赛之后,它从未完成,因为上一场比赛破坏了比赛中的某些东西
    struct foo
    {
        foo(std::atomic<int>& count)
            : _stop(false)
        {
            // std::promise<void> p;    // <-- moved to be a member
            std::future <void> f = p.get_future();
    
            // ...same as before...
        }
        void run(std::promise<void>& p)
        {
            // ... same ...
        }
    
        std::promise<void> p;   // <---
        std::thread _thread;
        bool _stop;
    };
    
    std::promise<void> p;
    std::future <void> f = p.get_future();
    _thread = std::thread(&foo::run, this, std::move(p));
    
    void run(std::promise<void> p)
    {
        p.set_value();
    }