阻塞队列(BlockingQueue)经常用于生产、消费者模式的编程当中。典型的应用是线程池,多个或一个生产者往线程池当中塞入任务,多个工作线程作为消费者从中获取任务。线程池当中有一个阻塞队列缓存所有的任务,提供添加,获取的基本接口,保证线程安全。因此阻塞队列是线程同步的一种基础设施,适用于生产、消费者模型。它的特点是简单通用、线程安全,适合组装更高层的设施,例如线程池。
阻塞队列的基本实现是:
pthread_mutex_t *mutex;
pthread_cond_t *cond;
std::deque<int> queue;
int dequeue(){
MutexLockGuardguard(mutex);
while(queue.empty()) //必须使用循环,因为有多个消费者
{
pthread_cond_wait(cond, mutex); //先解锁,然后等待。不会与enqueue死锁
//wait执行完以后加锁
}
assert(!queue.empty());
int top = queue.front();
queue.pop_front();
return queue;
}
void enqueue(int x){
{
MutexLockGuard guard(mutex);
queue.push_back(x);
}
pthread_cond_signal(cond);
}
代码不多,但是仍然有值得注意的地方。
1) 必须用while来等待条件变量,如果改成if(queue.empty())是有问题的。因为pthread_cond_wait会打开锁,等待唤醒。这段时间内,其他线程有可能竞争获胜,消费掉任务,导致任务队列为空。所以本线程还要老实等待。这叫做spurious wakeup。
2) 要使用pthread_cond_signal(cond),而不是pthread_cond_broadcast(cond),如果当前任务很少,会引起“惊群”。过分地唤醒工作线程。
3) 每一次enqueue都会唤醒线程,如果队列大小为0时才唤醒线程是有问题的。这会导致工作线程的懒惰。如果一下来了10个任务,线程调度时先调用了10次enqueue,然后才轮到工作线程dequeue。因为只有一次唤醒,只有1个工作线程运行。
如果不希望每次enqueue的时候都唤醒线程,可以再工作线程等待时去唤醒。
int dequeue(){
…
while(queue.empty()) //必须使用循环,因为有多个消费者
{
__sync_fetch_and_add(&thread_wait, 1); //增加等待的线程数
pthread_cond_wait(cond, mutex);
__sync_fetch_and_sub(&thread_wait, 1);
}
…
}
void dequeue(int x){
MutexLockGuardguard(mutex);
queue.push_back(x);
if (thread_wait>0)
pthread_cond_signal(cond);
}
最后添加一份windows下面的实现
#include <list>
#include <windows.h>
template<class Type>
class BlockQueue
{
public:
BlockQueue()
:task_event(NULL)
,wait_thread(0)
{
//create event
task_event = CreateEvent(NULL, FALSE, FALSE, "task event");
if (task_event == NULL) {
throw -1;
}
//create mutex
InitializeCriticalSection(&sec_task_que);
}
virtual ~BlockQueue(){
//destroy mutex
DeleteCriticalSection(&sec_task_que);
//destroy event
CloseHandle(task_event);
task_event = NULL;
}
void add(const Type& obj){
EnterCriticalSection(&sec_task_que);
task_que.push_back(obj);
//unlock task queue
LeaveCriticalSection(&sec_task_que);
if (wait_thread > 0){
SetEvent(task_event);
}
}
Type get(){
Type obj;
bool wait_task = true;
while (wait_task) {
//lock task queue
EnterCriticalSection(&sec_task_que);
if (task_que.size() > 0) {
obj = task_que.front();
task_que.pop_front();
wait_task =false;
} else {
wait_task = true;
}
//unlock task queue
LeaveCriticalSection(&sec_task_que);
if (wait_task) {
InterlockedExchangeAdd(&wait_thread, 1);
WaitForSingleObject(task_event, INFINITE);
InterlockedExchangeAdd(&wait_thread, -1);
}
}
return obj;
}
private:
CRITICAL_SECTION sec_task_que; //任务队列互斥量
void* task_event; //任务通知
std::list<Type> task_que; //任务队列
LONG wait_thread;
};
参考