Вопрос по c++11, c++ – состояние гонки в pthread_once ()?
у меня есть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)
send_promise
?
Anthony
Wait::_p
инициализируется? Я этого не вижу.
ildjarn
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
после уничтожения обещания другой поток пытается разбудить заблокированные потоки. Поэтому я думаю, что вы правильно определили проблему.
p.set_value()
) синхронизируется с возвратом изf.wait()
так что обещаниеshouldn't быть уничтоженным, пока значение не будет установлено ... но изменение кода для перемещения обещанияdoes кажется, чтобы исправить ошибку. Мне придется пересмотреть мою реализациюpromise::set_value()
...
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()
вызов.
set_value()
возвращается. Обратите внимание, что я не уверен, что там есть гонка (или что гонка разрешена стандартом), но это похоже на область, где могут возникнуть проблемы, еслиset_value
использует некоторое состояние изp
после того, как он выполнил любую сигнализацию, он разблокирует ожидание на будущее. Кроме того, даже если это оказывается так, я не уверен, что это будет ошибкой где-то в реализацииset_value
илиpthread
API, на которые он опирается, или ошибка в том, какpromise
используется в коде здесь.
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()
все еще активен.