c++ - pthread_once() 中的竞争条件?

标签 c++ c++11 pthreads future promise

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

编辑: 用一个将永远阻塞的示例应用程序更新了问题:

更新:如果我改用pthread_barrier,下面的代码不会阻塞。

我创建了一个测试应用来说明这一点:

基本上 foo 类创建了一个 thread,它在其 run 函数中设置了一个 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

我很确定我的代码没有做错任何事,对吧?

这是 pthread 实现的问题,还是 std::future/std::promise 实现的问题?

我的库版本是:

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)

最佳答案

确实,本地promise 的析构函数之间存在竞争条件。对象(在构造函数的末尾和线程对 set_value() 的调用。也就是说,set_value() 唤醒主线程,接下来将销毁 promise 对象,但 set_value() 函数尚未完成,并且死锁。

正在看C++11标准,不知道你的使用是否允许:

void promise<void>::set_value();

Effects: atomically stores the value r in the shared state and makes that state ready.

但在其他地方:

The set_value, set_exception, set_value_at_thread_exit, and set_exception_at_thread_exit member functions behave as though they acquire a single mutex associated with the promise object while updating the promise object.

set_value()调用对于其他函数(例如析构函数)应该是原子的?

恕我直言,我会说不。其效果相当于在其他线程仍在锁定互斥锁时销毁互斥锁​​。结果未定义。

解决方案是制作p比线程长。我能想到的两个解决方案:

  1. 制作p类(class)成员,正如迈克尔·伯尔在另一个答案中所建议的那样。

  2. 将 promise 移到线程中。

在构造函数中:

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不过,进入 promise 是强制性的。

并且线程函数接收的不是引用,而是移动的promise:

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

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

关于c++ - pthread_once() 中的竞争条件?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27746200/

相关文章:

c++ - 通过引用传递后值不会改变

c++ - 模板函数不同的结果取决于引用类型

c++ - 抽象类析构函数与继承的 "Shutdown"函数

c++ - C++ 有 "not equal compare and exchange"或 "fetch add on not equal"吗?

c - 什么是 PTHREAD_MUTEX_ADAPTIVE_NP

c - 以下程序的输出

c++ - 为什么队列的所有元素都一样,元素在posix线程中加入队列

c++ - 合并排序中的递归调用

c++ - 无法创建弹出菜单

从 GETTEXT 获取的 C++ 变量作为 mysql_query 变量