我有一个阻塞队列(我真的很难改变它的实现),我想测试它是否真的阻塞了。特别是,如果队列为空,则pop方法必须阻塞,并在执行push时立即解除阻塞。请参阅下面的伪C++11代码进行测试:
BlockingQueue queue; // empty queue
thread pushThread([]
{
sleep(large_delay);
queue.push();
});
queue.pop();显然它并不完美,因为即使延迟很大,整个线程pushThread也可能在pop被调用之前执行并终止,而且延迟越大,我必须等待测试结束的时间就越长。
如何正确地确保在调用push之前执行pop,即在push返回之前阻塞?
发布于 2017-11-18 03:52:28
如果不向BlockingQueue添加一些额外的状态和接口,我认为这是不可能的。
证据是这样的。您希望等到pop上的读取线程被阻塞。但是没有办法将其与将要执行pop的线程区分开来。无论您在调用pop之前或之后放置什么内容,这一点都是正确的。
如果你真的想以100%的可靠性修复这个问题,你需要在队列中添加一些状态,由队列的互斥锁来保护,这意味着“有人在等待”。然后,pop调用必须在原子释放互斥锁之前更新状态,并在内部条件变量上休眠。push线程可以获取互斥锁并等待,直到“有人在等待”。为了避免这里出现繁忙循环,您需要再次使用条件变量。
所有这些机器几乎和队列本身一样复杂,所以也许你也想测试一下……在这类多线程代码中,“代码覆盖率”--甚至可以说是单元测试本身--这样的概念有点崩溃了。有太多可能的操作交错。
在实践中,我可能会采用你最初的睡眠方法。
发布于 2017-11-18 06:09:25
template<class T>
struct async_queue {
T pop() {
auto l = lock();
++wait_count;
cv.wait( l, [&]{ return !data.empty(); } );
--wait_count;
auto r = std::move(data.front());
data.pop_front();
return r;
}
void push(T in) {
{
auto l = lock();
data.push_back( std::move(in) );
}
cv.notify_one();
}
void push_many(std::initializer_list<T> in) {
{
auto l = lock();
for (auto&& x: in)
data.push_back( x );
}
cv.notify_all();
}
std::size_t readers_waiting() {
return wait_count;
}
std::size_t data_waiting() const {
auto l = lock();
return data.size();
}
private:
std::queue<T> data;
std::condition_variable cv;
mutable std::mutex m;
std::atomic<std::size_t> wait_count{0};
auto lock() const { return std::unique_lock<std::mutex>(m); }
};或者诸如此类的。
在推送线程中,忙等待readers_waiting,直到它超过1。
在这一点上,您拥有锁,并且在解锁之前处于cv.wait中。做一个push。
从理论上讲,无限慢的读取器线程可能已经进入cv.wait,并且在调用push时仍在计算第一个lambda,但无限慢的读取器线程与阻塞的读取器线程没有什么不同……
然而,这确实会处理线程启动缓慢等问题。
除了调试之外,使用readers_waiting和data_waiting做任何事情通常都是一种代码气味。
发布于 2017-11-17 23:56:03
您可以使用std::condition_variable来完成此任务。cppreference.com的帮助页面实际上显示了一个非常好的消费者-生产者示例,它应该正是您正在寻找的:http://en.cppreference.com/w/cpp/thread/condition_variable
编辑:实际上,德语版本的cppreference.com有一个更好的例子:-) http://de.cppreference.com/w/cpp/thread/condition_variable
https://stackoverflow.com/questions/47354085
复制相似问题