我有一个阻塞队列(我真的很难改变它的实现),我想测试它实际阻塞。特别是,pop
方法必须在队列为空时阻止,并在执行push
后立即取消阻止。请参阅以下用于测试的伪C ++ 11代码:
BlockingQueue queue; // empty queue
thread pushThread([]
{
sleep(large_delay);
queue.push();
});
queue.pop();
显然它并不完美,因为可能会发生整个线程pushThread
被执行并在调用pop
之前终止,即使延迟很大,延迟越大我越多必须等待测试结束。
如何正确确保pop
在调用push
之前执行,并且在push
返回之前是阻塞?
答案 0 :(得分:1)
如果不向BlockingQueue添加一些额外的状态和接口,我不相信这是可能的。
证明就是这样的。您希望等到pop
上的阅读主题被阻止。但是没有办法区分它和即将执行pop
的线程。无论你在调用pop
之前或之后放置什么,这都是正确的。
如果你真的想以100%的可靠性修复它,你需要在队列中添加一些状态,由队列的互斥锁保护,这意味着“有人在等待”。然后,pop
调用必须在它原子地释放互斥锁之前更新该状态,并在内部条件变量上进入休眠状态。 push
线程可以获取互斥锁并等到“某人正在等待”。为避免繁忙循环,您需要再次使用条件变量。
所有这些机器几乎和队列本身一样复杂,所以也许你也想测试它......这种多线程代码就像“代码覆盖”这样的概念 - 甚至可以称为单元测试本身 - 分解一下。有太多可能的交错操作。
在实践中,我可能会采用你最初的睡眠方法。
答案 1 :(得分:1)
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
用于除调试以外的任何其他操作通常都是代码味道。
答案 2 :(得分:0)
您可以使用std::condition_variable
来完成此操作。 cppreference.com的帮助页面实际上显示了一个非常好的cosumer-producer示例,它应该是您正在寻找的内容:http://en.cppreference.com/w/cpp/thread/condition_variable