竞争条件在调用pthread_once()?(race-condition in pthread_o

2019-06-25 00:58发布

我有一个std::future在一个线程这是在等待std::promise在另一个线程被设置。

编辑: 更新了一个范例应用程序,它会永远阻塞的问题:

更新:如果我使用一个pthread_barrier代替,下面的代码不会阻止。

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

非常基本类foo创建一个thread来设置一个promise在其运行的功能,并在构造为等待promise进行设置。 一旦设定,它增加的atomic

然后,我创建了一堆这些的foo对象,撕裂下来,然后检查我的count

#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);
}

如果我跑这一个循环与1000个线程,它只有直到比赛发生时执行了几次和一个futures永远不会醒来的,因此应用程序被永远困。

# this loop never completes
$ for i in {1..1000}; do ./a.out 1000; done

如果我现在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 ::承诺实施?

我的图书馆版本是:

libstdc++.so.6
libc.so.6 (GNU C Library stable release version 2.11.1 (20100118))
libpthread.so.0 (Native POSIX Threads Library by Ulrich Drepper et al Copyright (C) 2006)

Answer 1:

事实上,有当地的析构函数之间的竞争条件promise对象(在构造函数的结束和调用set_value()从线程,也就是说, set_value()唤醒主胎面,这只是接下来的破坏承诺对象,但set_value()函数尚未完成,和死锁。

读C ++ 11个标准,我不知道是否允许您使用:

void promise<void>::set_value();

效果:原子存储在共享状态中的值r,并使该状态就绪。

但别的地方:

的SET_VALUE,set_exception,set_value_at_thread_exit和set_exception_at_thread_exit成员函数表现得好像他们获得同时更新承诺对象与所述承诺对象相关联的单个互斥

set_value()的调用应该是原子至于其他功能,如析构函数?

恕我直言,我会说不。 效果绝不会比摧毁一个互斥体,而其他线程仍在锁定它。 其结果是不确定的。

该解决方案将是使p活得比线程。 有两种解决方案,我能想到的:

  1. 使p类中的一员,就像迈克尔·伯尔在其他答案建议。

  2. 移动承诺到线程。

在构造函数:

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

顺便说一句,你不需要调用bind ,(线程构造器已经超负荷运转),或来电std::move移动线(正确的价值已经是一个r值)。 要调用std::move入的承诺是强制性的,但。

和线程功能没有收到一个参考,但移动承诺:

void run(std::promise<void> p)
{
    p.set_value();
}

我想,这也正是C ++ 11定义了两种不同类: promisefuture :您将承诺到线程,但你保持未来复苏的结果。



Answer 2:

尝试移动std::promise<void> 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;
};

我beleive什么可能发生的情况是,你进入其中一个种族p在同时构造破坏p.set_value()作用于参照该promise 。 事情发生内部set_value()而它的完成了/清理; 作用于参考已经被破坏std::promise在破坏一些国家pthread库。

这仅仅是猜测 - 我没有,在目前重现问题的系统随时访问。 但要做出p成员将确保其使用寿命过去的完井延伸set_value()调用。



文章来源: race-condition in pthread_once()?