线程安全的BlockingQueue

阻塞队列(BlockingQueue)经常用于生产、消费者模式的编程当中。典型的应用是线程池,多个或一个生产者往线程池当中塞入任务,多个工作线程作为消费者从中获取任务。线程池当中有一个阻塞队列缓存所有的任务,提供添加,获取的基本接口,保证线程安全。因此阻塞队列是线程同步的一种基础设施,适用于生产、消费者模型。它的特点是简单通用、线程安全,适合组装更高层的设施,例如线程池。

 

阻塞队列的基本实现是:

pthread_mutex_t *mutex;
pthread_cond_t *cond;
std::deque 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 
#include 

template
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 task_que;						//任务队列
	LONG wait_thread;
};

参考

发布一个 Linux 下的 C++ 多线程库

《Linux多线程服务端编程:使用muduo C++网络库》


你可能感兴趣的:(C++)