pthread_once() 中的竞争条件?
我在一个线程中有一个 std::future
,它正在等待另一个线程中设置的 std::promise
.
I have a std::future
in one thread which is waiting on a std::promise
being set in another thread.
用一个示例应用程序更新了问题,该应用程序将永远阻止:
更新:如果我使用 pthread_barrier
代替,下面的代码不会阻塞.
UPDATE: If I use a pthread_barrier
instead, the below code does not block.
我创建了一个测试应用程序来说明这一点:
I have created a test-app which illustrates this:
非常基本的 foo
类创建了一个 thread
,它在它的 run 函数中设置了一个 promise
,并在构造函数中等待那个 承诺
被设置.设置后,它会增加 atomic
计数
Very basically class foo
creates a thread
which sets a promise
in its run function, and waits in the constructor for that promise
to be set. Once set, it increments an atomic
count
然后我创建了一堆这样的 foo
对象,将它们拆掉,然后检查我的 count
.
I then create a bunch of these foo
objects, tear them down, and then check my 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
之一永远不会被唤醒,因此应用程序卡住了永远.
If I run this in a loop with 1000 threads, it only has to execute it a few times until a race occurs and one of the futures
is never woken up, and therefore the app gets stuck forever.
# this loop never completes
$ for i in {1..1000}; do ./a.out 1000; done
如果我现在 SIGABRT
应用程序,结果堆栈跟踪显示它卡在 future::wait
堆栈跟踪如下:
If I now SIGABRT
the app, the resulting stack trace shows it's stuck on the future::wait
The stack trace is below:
// 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
我很确定我的代码没有做错任何事情,对吧?
I'm pretty sure that I'm not doing anything wrong in my code, right?
这是 pthread 实现的问题,还是 std::future/std::promise 实现的问题?
Is this an issue with the pthread implementation, or the std::future/std::promise implementation?
我的库版本是:
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()
code> 函数还没执行完,就死锁了.
Indeed, there is a race condition between the destructor of the local promise
object (at the end of the constructor and the call to set_value()
from the thread. That is, set_value()
wakes the main tread, that just next destroys the promise object, but the set_value()
function has not yet finished, and dead-locks.
阅读 C++11 标准,我不确定是否允许您使用:
Reading the C++11 standard, I'm not sure if your use is allowed:
void promise
效果:以原子方式将值 r 存储在共享状态中并使该状态准备就绪.
Effects: atomically stores the value r in the shared state and makes that state ready.
但在其他地方:
set_value、set_exception、set_value_at_thread_exit 和 set_exception_at_thread_exit 成员函数的行为就像它们在更新承诺对象时获取与承诺对象关联的单个互斥锁一样.
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()
调用是否应该是原子的?
Are set_value()
calls supposed to be atomic with regards to other functions, such as the destructor?
恕我直言,我会说不.效果类似于在其他线程仍在锁定它时销毁互斥锁??.结果未定义.
IMHO, I'd say no. The effects would be comparable to destroying a mutex while other thread is still locking it. The result is undefined.
解决方案是让 p
超过线程.我能想到的两个解决方案:
The solution would be to make p
outlive the thread. Two solutions that I can think of:
让
p
成为班级的成员,正如 Michael Burr 在另一个答案中建议的那样.
Make
p
a member of the class, just as Michael Burr suggested in the other answer.
将承诺移到线程中.
在构造函数中:
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
的调用是强制性的.
BTW, you don't need the call to bind
, (the thread constructor is already overloaded), or call to std::move
to move the thread (the right value is already an r-value). The call to std::move
into the promise is mandatory, though.
而且线程函数没有收到引用,而是移动了promise:
And the thread function does not receive a reference, but the moved promise:
void run(std::promise<void> p)
{
p.set_value();
}
我认为这正是 C++11 定义两个不同类的原因:promise
和 future
:你将承诺移动到线程中,但你保留未来恢复结果.
I think that this is precisely why C++11 defines two different classes: promise
and future
: you move the promise into the thread, but you keep the future to recover the result.
相关文章