Вопрос по c++11, c++ – состояние гонки в pthread_once ()?

15

у меня естьstd::future в одной ветке, которая ждет наstd::promise устанавливается в другой теме.

EDIT: Updated the question with an exemplar app which will block forever:

UPDATE: Если я используюpthread_barrier вместо этого приведенный ниже кодnot блок.

Я создал тестовое приложение, которое иллюстрирует это:

Очень в основном класс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
    [email protected]@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 <[email protected]>) 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 :: обещать?

Мои версии библиотеки:

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)
@ Энтони-Арнольд, дальнейшие подробности добавлены в теле оп Steve Lorimer
@ildjarn, будет создан по умолчанию при создании объекта Wait Steve Lorimer
Можем ли мы увидеть больше внутренних органовsend_promise? Anthony
Эээ, когда естьWait::_p инициализируется? Я этого не вижу. ildjarn
@ildjarn, справочная документация, которая заставляет меня полагать, что конструкция по умолчанию достаточна:stdthread.co.uk/doc/headers/future/promise/… Steve Lorimer

Ваш Ответ

2   ответа
8

promise объект (в конце конструктора и вызовset_value() из потока. То есть,set_value() пробуждает основной шаг, который только затем разрушает объект обещания, но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 пережить нить. Два решения, которые я могу придумать:

Make p a member of the class, just as Michael Burr suggested in the other answer.

Move the promise into the thread.

В конструкторе:

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 определяет два разных класса:promise а такжеfuture: вы перемещаете обещание в поток, но сохраняете будущее, чтобы восстановить результат.

Да я пришел к такому же выводу
Ах, ноp.set_value() делает государство готовымthen будит любые ожидающие темы ([futures.state] / 6). Если государство готовоbefore конструктор начинает ждать, затемf.wait() немедленно возвращается иp после уничтожения обещания другой поток пытается разбудить заблокированные потоки. Поэтому я думаю, что вы правильно определили проблему.
Однако, как показывает трассировка стека, при зависании конструктор все еще ожидаетf.wait() так что обещание еще не разрушено ... странно.
@Jonathan: я подозреваю (на самом деле больше похоже на дикую догадку), что никогда не завершатьf.wait() следует за тем, кто участвует в гонке, и что он никогда не завершится, потому что предыдущая раса что-то испортила вpthread библиотека.
Хм, [futures.state] / 9 говорит, что готовит государство (вp.set_value()) синхронизируется с возвратом изf.wait() так что обещаниеshouldn't быть уничтоженным, пока значение не будет установлено ... но изменение кода для перемещения обещанияdoes кажется, чтобы исправить ошибку. Мне придется пересмотреть мою реализациюpromise::set_value() ...
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;
};

Я верю, что то, что может случиться, это то, что вы попадаете в гонку, гдеp уничтожается в конструкторе в то время какp.set_value() действует на ссылку на этоpromise, Что-то происходит внутриset_value() в то время как это заканчивает / убирает; действующий по ссылке на уже уничтоженныйstd::promise развращает какое-то государство вpthread библиотека.

Это всего лишь предположение - у меня нет готового доступа к системе, которая воспроизводит проблему в данный момент. Но делаяp член обеспечит, чтобы его срок жизни значительно превышал время завершенияset_value() вызов.

Но код не покидает конструктор до того, как потоки вызовут p.set_value (). Это ждет
Ах, верно. OF, реализация может быть зависимой. Возможно, сломанная реализация
@ fork0: но как только разблокируется, конструктор может закончить раньшеset_value() возвращается. Обратите внимание, что я не уверен, что там есть гонка (или что гонка разрешена стандартом), но это похоже на область, где могут возникнуть проблемы, еслиset_value использует некоторое состояние изp после того, как он выполнил любую сигнализацию, он разблокирует ожидание на будущее. Кроме того, даже если это оказывается так, я не уверен, что это будет ошибкой где-то в реализацииset_value илиpthread API, на которые он опирается, или ошибка в том, какpromise используется в коде здесь.
@ fork0, я думаю, что есть гонка даже с правильной реализацией, см. мой второй комментарий кrodrigo's answer
Стандарт требует, чтобыpromise::set_value() удерживать мьютекс (или действовать так, как если бы мьютекс удерживался), пока он обновляетpromise объект (30.6.5 / 2). Еслиpromise разрушается в то время какset_value держит мьютекс вpromise объект, конечно, что приводит к UB. Следовательно, это небезопасно дляpromise быть уничтоженным доafter promise::set_value() возвращается иpromise ссылка больше не используется. Я согласен с Джонатаном, что реализация не обязанаpromise::set_value() безопасно перед лицомpromise будучи уничтоженным в то время какset_value() все еще активен.

Похожие вопросы