阻塞队列
什么是阻塞队列
-
基本概念
顾名思义,就是每次取数据的时候需要保证队列中有数据,如果没有则需要阻塞等待。这里触发的唯一条件即"队列中存在数据时",此时执行取数据的线程将被CPU唤醒
阻塞队列: 将生产者-消费者模型进行封装,一般使用循环数组,这里为了简化实现,用STL的队列实现
生产者-消费者模型: 并发编程中的经典模型。以多线程为例,为了实现线程间数据同步,生产者线程与消费者线程共享一个缓冲区,其中生产者线程往缓冲区中push消息,消费者线程从缓冲区中pop消息。
-
应用举例:日志系统
程序运行过程中无非就是提供服务/处理客户端的请求,而运行过程中可能会获取到请求数据,生成新数据,传递数据等等,甚至可能会出现各种错误,日志的作用就是将运行过程中产生的各种情况或者问题,对于开发人员来说这些数据是有用的,那么有必要在产生这些数据的时候保存在服务器本地目录中用来分析和判断。
串行/同步实现日志存储
这种方式很容易理解,如果产生了某种数据,我们可以即时存下来,但是问题就是服务器大多数时候用来处理请求或者业务逻辑,而日志数据的存储可能会造成长时间的阻塞,导致服务器性能下降,因此我们有必要利用多线程的方式异步地存储,不影响业务流程地正常执行
异步/多线程日志
我们可以将每次日志地存储当作一次I/O操作,这些操作也可以视为"请求",程序运行过程中可能会产生非常多地这种"请求",很容易想到地一种方式是构建一个队列,将所有请求即时地存入队列中,待可执行线程去队列中获取请求并完成任务。那么这一系列操作可以视为生产者消费者模型,为什么呢?生产者即为产生日志存储"请求"的线程,消费者即为处理这些“请求”的线程,如何设计这样的异步多线程日志系统,可以参考一些开源的方法。
线程间通信 (多线程的条件变量: 注意与锁一起使用)
-
线程 API
pthread_t // 线程 类型变量 pthread_create()
-
锁
pthread_mutex_t // 锁的类型变量 pthread_mutex_lock() pthread_mutex_unlock()
-
条件变量 API
pthread_cond_init() pthread_cond_destory() pthread_cond_broadcast() pthread_cond_wait()
-
生产者消费者通用模型
#include <pthread.h> #include <queue> // c++ stl 方便写任务队列用stl代替 struct task{ // data }; queue<task*> tasks; // 定义任务/缓冲队列,用于存放需要处理的任务 pthread_cond_t qready = PTHREAD_COND_INITIALIZER; pthread_mutex_t qlock = PTHREAD_MUTEX_INITIALIZER; // 消费者线程 void process_thread() { struct task *temp; for(;;) // 缩小锁作用域 { // 加锁,访问共享区(任务队列) pthread_mutex_lock(&qlock); while (tasks.empty()) // 没有任务 { // 如果条件变量不满足,自动解锁,进入阻塞状态; // 如果条件变量满足,则再次获得锁 pthread_cond_wait(&qready, &qlock); } temp = tasks.front();// 取出任务 tasks.pop_front();// 从任务队列删除任务,结束对共享区的访问 pthread_mutex_unlock(&qready); // 处理任务 // ... } } // 生产者线程 void enqueue_thread(struct task* new_task) { pthread_mutex_lock(&qlock); // 加锁 tasks.emplace(new_task); // 访问共享区 pthread_mutex_unlock(&qlock) // 解锁 // 此时加入数据后需要激活条件变量,通知其他线程 pthread_cond_signal(&qready); // 只有一个线程可以被唤醒 }
阻塞队列实现
阻塞队列将生产者-消费者模型封装在类里,并实现线程安全的队列操作,具体的数据结构可以使用循环数组或STL的queue
具体实现不再赘述,本质就是将生产者封装为阻塞队列的入队列操作,将消费者封装为阻塞队列的出队列操作
思考的问题,任务队列的操作需要保证线程安全,队列作为共享资源,读操作需要加锁,为什么push和pop进行写操作时需要用条件变量?解释:因为这些操作我们需要保证能正常完成,即在队列满的时候也得保证等待有任务pop之后将当前任务push进去。pop操作也必须等待有任务进入队列时取出数据。因此在极端情况下push需要等待pop的条件变量触发,pop也需要等待push的条件变量触发
- 多线程同步的封装
#include <pthread.h>
#include <semaphore.h>
class mutexCLS final
{
private:
pthread_mutex_t m_mutex;
public:
mutexCLS(); // 初始化锁
~mutexCLS(); // 删除锁
bool lock(); // 上锁
bool unlock(); // 解锁
pthread_mutex_t* get(); // 获取锁的指针
};
class semCLS final
{
private:
sem_t m_sem;
public:
semCLS();
semCLS(int num);
~semCLS();
// 信号量减一
bool wait();
// 信号量加1
bool post();
};
class condCLS
{
private:
pthread_cond_t m_cond;
public:
condCLS();
~condCLS();
// 等待信号
bool wait(pthread_mutex_t *m_mutex);
bool timewait(pthread_mutex_t *m_mutex, struct timespec t);
// 通知信号
bool signal();
bool broadcast();
};
mutexCLS::mutexCLS()
{
if (pthread_mutex_init(&m_mutex, NULL) != 0)
throw std::exception();
}
mutexCLS::~mutexCLS()
{
pthread_mutex_destroy(&m_mutex);
}
bool mutexCLS::lock()
{
return pthread_mutex_lock(&m_mutex) == 0;
}
bool mutexCLS::unlock()
{
return pthread_mutex_unlock(&m_mutex) == 0;
}
pthread_mutex_t* mutexCLS::get()
{
return &m_mutex;
}
// semaphore
semCLS::semCLS()
{
if (sem_init(&m_sem, 0, 0) != 0)
throw std::exception();
}
semCLS::semCLS(int num)
{
if (sem_init(&m_sem, 0, num) != 0)
throw std::exception();
}
semCLS::~semCLS()
{
sem_destroy(&m_sem);
}
bool semCLS::wait()
{
return sem_wait(&m_sem) == 0;
}
bool semCLS::post()
{
return sem_post(&m_sem) == 0;
}
// condition
condCLS::condCLS()
{
if (pthread_cond_init(&m_cond, NULL) != 0)
throw std::exception();
}
condCLS::~condCLS()
{
pthread_cond_destroy(&m_cond);
}
bool condCLS::wait(pthread_mutex_t* m_mutex)
{
return pthread_cond_wait(&m_cond, m_mutex) == 0;
}
bool condCLS::timewait(pthread_mutex_t* m_mutex, struct timespec t)
{
return pthread_cond_timedwait(&m_cond, m_mutex, &t) == 0;
}
bool condCLS::signal()
{
return pthread_cond_signal(&m_cond) == 0;
}
bool condCLS::broadcast()
{
return pthread_cond_broadcast(&m_cond) == 0;
}
- 阻塞队列
#include "../lock/lock.h"
#include <iostream>
#include <queue>
template<class T>
class blockQueue
{
private:
// 锁和条件变量
mutexCLS m_mutex;
condCLS m_cond;
// 任务队列
queue<T> m_queue;
public:
blockQueue();
~blockQueue();
bool front(T &item); // 保证线程安全
bool back(T &item); // 保证线程安全
bool push(const T &item); // 线程安全
bool pop(T &item); // 保证线程安全
int size(); // 保证线程安全
bool empty(); // 保证线程安全
};
template<class T>
blockQueue<T>::blockQueue()
{
}
template<class T>
blockQueue<T>::~blockQueue()
{
m_mutex.lock();
m_queue = queue<T>(); // 访问共享资源 清空队列
m_mutex.unlock();
}
template<class T>
bool blockQueue<T>::front(T& item)
{
m_mutex.lock();
if (m_queue.size() == 0){
m_mutex.unlock();
return false;
}
item = m_queue.front();
m_mutex.unlock();
return true;
}
template<class T>
bool blockQueue<T>::back(T& item)
{
m_mutex.lock();
if (m_queue.size() == 0){
m_mutex.unlock();
return false;
}
item = m_queue.back();
m_mutex.unlock();
return true;
}
// 不考虑列队容量上限
template<class T>
bool blockQueue<T>::push(const T& item)
{
m_mutex.lock();
m_queue.push(item);
m_cond.broadcast();
m_mutex.unlock();
return true;
}
template<class T>
bool blockQueue<T>::pop(T& item)
{
m_mutex.lock();
while (m_queue.size() <= 0){
if (!m_cond.wait(m_mutex.get())) // wait函数调用失败
{
m_mutex.unlock();
return false;
}
}
item = m_queue.front();
m_queue.pop();
m_mutex.unlock();
return true;
}
template<class T>
bool blockQueue<T>::empty()
{
int size = 0;
m_mutex.lock();
size = m_queue.size();
m_mutex.unlock();
return size == 0;
}
template<class T>
int blockQueue<T>::size()
{
int size = 0;
m_mutex.lock();
size = m_queue.size();
m_mutex.unlock();
return size;
}