15

std::future別のスレッドで設定されるのを待っているスレッドがありますstd::promise

編集: 永久にブロックする模範的なアプリで質問を更新しました:

更新:代わりにa を使用するpthread_barrierと、以下のコードはブロックされません。

これを説明するテストアプリを作成しました:

非常に基本的に、クラスは実行関数に aを設定するa をfoo作成し、コンストラクターでそれが設定されるのを待ちます。設定すると、カウントがインクリメントされますthreadpromisepromiseatomic

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済み、そのうちの 1 つが起動されないため、アプリが永久に動かなくなります。

# 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)
4

2 に答える 2

8

実際、ローカルpromiseオブジェクトのデストラクタ (コンストラクタの最後とset_value()スレッドからの呼び出し) の間には競合状態があります。つまりset_value()、メイン トレッドを起動し、次に promise オブジェクトを破棄しますが、set_value()関数はそうではありません。まだ終わっていない、デッドロック。

C++11 標準を読むと、使用が許可されているかどうかわかりません。

void promise<void>::set_value();

効果: 値 r をアトミックに共有状態に格納し、その状態を準備します。

しかし、別の場所:

set_value、set_exception、set_value_at_thread_exit、および set_exception_at_thread_exit メンバー関数は、promise オブジェクトの更新中に、promise オブジェクトに関連付けられた単一のミューテックスを取得するかのように動作します。

set_value()呼び出しは、デストラクタなどの他の関数に関してアトミックであると想定されていますか?

私見、私はノーと言うでしょう。その影響は、他のスレッドがまだロックしている間にミューテックスを破棄することに匹敵します。結果は未定義です。

p解決策は、スレッドを存続させることです。私が考えることができる2つの解決策:

  1. Michael Burrpが他の回答で提案したように、クラスのメンバーを作成します。

  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 への呼び出しは必須です。

そして、スレッド関数は参照を受け取りませんが、移動されたプロミス:

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

これこそまさに、C++11 が 2 つの異なるクラスを定義する理由だと思います:promisefuture: プロミスをスレッドに移動しますが、結果を回復するために未来を保持します。

于 2012-07-12T08:57:48.463 に答える
5

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

thatへの参照に基づいて動作しているpときに、コンストラクターで が破棄されるという競合に陥る可能性があると私は信じています。仕上げ/片付け中に内部で何かが発生します。すでに破棄されているものへの参照に作用すると、ライブラリ内の一部の状態が破損します。p.set_value()promiseset_value()std::promisepthread

これは推測にすぎません。現時点では、問題を再現するシステムにアクセスする準備ができていません。ただし、メンバーを作成すると、呼び出しpの完了後もその有効期間が長くなることが保証されます。set_value()

于 2012-07-12T07:37:04.930 に答える