>>基础知识
C++ 的语言机制保证:当一个对象创建的时候,自动调用构造函数,当对象超出作用域的时候会自动调用析构函数
RAII:Resource Acquisition is Initialization,"资源获取即初始化"
- RAII 的核心思想是将资源或者状态与对象的生命周期绑定,通过C++的语言机制,实现资源和状态的安全管理
- RAII最好的例子:智能指针
- 在构造函数中申请分配资源,在析构函数中释放资源
- 使用类来管理资源,将资源和对象的生命周期绑定
1.线程池
线程池是由服务器预先创建的一组子线程,线程池中的线程数量应该和 CPU 数量差不多。线程池中的所有子线程都运行着相同的代码。当有新的任务到来时,主线程将通过某种方式选择线程池中的某一个子线程来为之服务。相比与动态的创建子线程,选择一个已经存在的子线程的代价显然要小得多。至于主线程选择哪个子线程来为新任务服务,则有多种方式:
① 主线程使用某种算法来主动选择子线程。最简单、最常用的算法是随机算法和 Round Robin(轮流选取)算法,但更优秀、更智能的算法将使任务在各个工作线程中更均匀地分配,从而减轻服务器 的整体压力。
② 主线程和所有子线程通过一个共享的工作队列来同步,子线程都睡眠在该工作队列上。当有新的任务到来时,主线程将任务添加到工作队列中。这将唤醒正在等待任务的子线程,不过只有一个子线程将获得新任务的”接管权“,它可以从工作队列中取出任务并执行之,而其他子线程将继续睡眠在工作队列上。
线程池中的线程数量最直接的限制因素是中央处理器(CPU)的处理器(processors/cores)的数量N :如果你的CPU是4-cores的,对于CPU密集型的任务(如视频剪辑等消耗CPU计算资源的任务)来说,那线程池中的线程数量最好也设置为4(或者+1防止其他因素造成的线程阻塞);对于IO密集型的任务,一般要多于CPU的核数,因为线程间竞争的不是CPU的计算资源而是IO,IO的处理一般较慢,多于cores数的线程将为CPU争取更多的任务,不至在线程处理IO的过程造成CPU空闲导致资源浪费。
- 空间换时间,浪费服务器的硬件资源,换取运行效率。
- 池是一组资源的集合,这组资源在服务器启动之初就被完全创建好并初始化,这称为静态资源。
- 当服务器进入正式运行阶段,开始处理客户请求的时候,如果它需要相关的资源,可以直接从池中获取,无需动态分配。
- 当服务器处理完一个客户连接后,可以把相关的资源放回池中,无需执行系统调用释放资源。
1.1 线程池类定义
- 线程处理函数和运行函数设置为私有属性
// 线程池类,将它定义为模板类是为了代码复用,模板参数T是任务类
template<typename T>
class threadpool {
public:
/*thread_number是线程池中线程的数量,max_requests是请求队列中最多允许的、等待处理的请求的数量*/
threadpool(int thread_number = 8, int max_requests = 10000);
~threadpool();
bool append(T* request);
private:
/*工作线程运行的函数,它不断从工作队列中取出任务并执行之*/
static void* worker(void* arg);
void run();
private:
// 线程的数量
int m_thread_number;
// 描述线程池的数组,大小为m_thread_number
pthread_t * m_threads;
// 请求队列中最多允许的、等待处理的请求的数量
int m_max_requests;
// 请求队列
std::list< T* > m_workqueue;
// 保护请求队列的互斥锁
locker m_queuelocker;
// 是否有任务需要处理
sem m_queuestat;
// 是否结束线程
bool m_stop;
};
1.2 线程池创建与回收
- 构造函数中创建线程池,pthread_create函数中将类的对象作为参数传递给静态函数(worker),在静态函数中引用这个对象,并且调用其动态方法(run)
- 类对象传递时用this指针,传递给静态函数
template< typename T >
threadpool< T >::threadpool(int thread_number, int max_requests) :
m_thread_number(thread_number), m_max_requests(max_requests),
m_stop(false), m_threads(NULL) {
if((thread_number <= 0) || (max_requests <= 0) ) {
throw std::exception();
}
// 线程id初始化
m_threads = new pthread_t[m_thread_number];
if(!m_threads) {
throw std::exception();
}
// 创建thread_number 个线程,并将他们设置为脱离线程。
for ( int i = 0; i < thread_number; ++i ) {
printf( "create the %dth thread\n", i);
// 循环创建线程,并将工作线程按要求进行运行
if(pthread_create(m_threads + i, NULL, worker, this ) != 0) {
delete [] m_threads;
throw std::exception();
}
// 将线程进行分离后,不用单独对工作线程进行回收
if( pthread_detach( m_threads[i] ) ) {
delete [] m_threads;
throw std::exception();
}
}
}
template< typename T >
threadpool< T >::~threadpool() {
delete [] m_threads;
m_stop = true;
}
1.3 向请求队列中添加任务
通过 list 容器创建请求队列,向队列中添加任务时,通知互斥锁保证线程安全,添加完成后通过信号量提醒有任务要处理,最后注意线程同步。
template< typename T >
bool threadpool< T >::append( T* request )
{
// 操作工作队列时一定要加锁,因为它被所有线程共享。
m_queuelocker.lock();
// 根据硬件,预先设置请求队列的最大值
if ( m_workqueue.size() > m_max_requests ) {
m_queuelocker.unlock();
return false;
}
// 添加任务
m_workqueue.push_back(request);
m_queuelocker.unlock();
// 信号量提醒有任务要处理
m_queuestat.post();
return true;
}
1.4 线程处理函数
- 内部访问私有成员函数run,完成线程处理
- 类对象传递时用 this 指针,传递给静态函数后,将其转换为线程池类,并调用私有成员函数run
template< typename T >
void* threadpool< T >::worker( void* arg )
{
// 将参数强转为线程池类,调用成员方法
threadpool* pool = ( threadpool* )arg;
pool->run();
return pool;
}
1.5 run执行任务
主要实现,工作线程从请求队列中取出某个任务进行处理,注意线程同步
template< typename T >
void threadpool< T >::run() {
while (!m_stop) {
// 信号量等待
m_queuestat.wait();
// 被唤醒后先加互斥锁
m_queuelocker.lock();
if ( m_workqueue.empty() ) {
m_queuelocker.unlock();
continue;
}
// 从请求队列中取出第一个任务
T* request = m_workqueue.front();
m_workqueue.pop_front();
m_queuelocker.unlock();
if ( !request ) {
continue;
}
// process(模板类中的方法,这里是http类)进行处理
request->process();
}
}
1.6 注意事项【总结来源两猿社的最新版Web服务器项目详解 - 03 半同步半反应堆线程池(下)】
pthread_create的函数原型
#include <pthread.h>
int pthread_create (pthread_t *thread_tid,//返回新生成的线程的id
const pthread_attr_t *attr,//指向线程属性的指针,通常设置为NULL
void * (*start_routine) (void *),//处理线程函数的地址
void *arg);//start_routine()中的参数
在 pthread_create函数原型中的第三个参数,为函数指针,指向处理线程函数的地址。且该函数为静态函数,所以在处理线程函数为类成员函数时,需要将其设置为静态成员函数。
// 线程池类,将它定义为模板类是为了代码复用,模板参数T是任务类
template<typename T>
class threadpool {
private:
/*工作线程运行的函数,它不断从工作队列中取出任务并执行之*/
static void* worker(void* arg);
public:
...
};
【原因】
- pthread_create的函数原型中的第三个参数的类型为函数指针,指向的线程处理函数参数类型为(void *)。
- 线程函数若为类成员函数,则this指针会作为默认参数被传进函数中,无法和线程函数参数(void *)匹配,无法编译。
- 由于静态成员函数里面没有this指针,能解决线程函数参数(void*)匹配问题。
1.7 信号量
信号量是一种特殊的变量,它只能取自然数值并且只支持两种操作:等待(P)和信号(V)
假设有信号量SV,对其的P、V操作如下:
- P,若SV的值大于0,则将其减一;若SV的值为0,则挂起执行;
- V,若有其他进行因为等待SV而挂起,则唤醒;若没有,则将SV值加一
信号量的取值可以是任何自然数,最常用的,最简单的信号量是二进制信号量,只有0和1两个值。
信号量的类型 sem_t
int sem_init(sem_t *sem, int pshared, unsigned int value);
- 初始化信号量
- 参数:
- sem : 信号量变量的地址
- pshared : 0 用在线程间 ,非0 用在进程间
- value : 信号量中的值
int sem_destroy(sem_t *sem);
- 销毁信号量
int sem_wait(sem_t *sem);
- 对信号量加锁,调用一次对信号量的值-1,如果值为0,就阻塞
int sem_post(sem_t *sem);
- 对信号量解锁,调用一次对信号量的值+1
以上,成功返回0,失败返回errno
1.8 条件变量
条件变量提供了一种线程间的通知机制, 当某个共享数据达到某个值时,唤醒等待这个共享数据的线程。
条件变量的类型 pthread_cond_t
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
- 用于初始化条件变量
int pthread_cond_destroy(pthread_cond_t *cond);
- 销毁条件变量
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
- 等待,调用了该函数,线程会阻塞。
- 用于等待目标条件变量。该函数调用时需要传入 mutex 参数(加锁的互斥锁),函数执行时,先把
调用线程放入条件变量的请求队列,然后将互斥锁mutex解锁;当函数成功返回为0时,互斥锁会再次
被锁上,也就是说函数内部会有一次解锁和加锁操作
int pthread_cond_broadcast(pthread_cond_t *cond);
- 唤醒所有的等待的线程
- 以广播的方式唤醒所有等待目标条件变量的线程
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
- 等待多长时间,调用了这个函数,线程会阻塞,直到指定的时间结束。
int pthread_cond_signal(pthread_cond_t *cond);
- 唤醒一个或者多个等待的线程
1.9 互斥量
互斥锁,也称互斥量,可以保护关键代码段,以确保独占式访问。
- 当进入关键代码段,获得互斥锁将其加锁;
- 离开关键代码段,唤醒等待该互斥锁的线程。
互斥量的类型 pthread_mutex_t
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
- 初始化互斥量
- 参数 :
- mutex : 需要初始化的互斥量变量
- attr : 互斥量相关的属性,NULL
- restrict : C语言的修饰符,被修饰的指针,不能由另外的一个指针进行操作。
pthread_mutex_t *restrict mutex = xxx;
pthread_mutex_t * mutex1 = mutex;
int pthread_mutex_destroy(pthread_mutex_t *mutex);
- 释放互斥量的资源
- 销毁互斥锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
- 以原子操作方式给互斥锁加锁
- 加锁,阻塞的,如果有一个线程加锁了,那么其他的线程只能阻塞等待
int pthread_mutex_unlock(pthread_mutex_t *mutex);
- 以原子操作方式给互斥锁解锁
- 解锁
int pthread_mutex_trylock(pthread_mutex_t *mutex);
- 尝试加锁,如果加锁失败,不会阻塞,会直接返回。
以上,成功返回0,失败返回errno
1.10 功能
锁机制的功能:实现多线程同步,通过锁机制,确保任一时刻只能有一个线程能进入关键代码段
完整代码:
threadpool.h
#ifndef THREADPOOL_H
#define THREADPOOL_H
#include <list>
#include <cstdio>
#include <exception>
#include <pthread.h>
#include "locker.h"
// 线程池类,将它定义为模板类是为了代码复用,模板参数T是任务类
template<typename T>
class threadpool {
public:
/*thread_number是线程池中线程的数量,max_requests是请求队列中最多允许的、等待处理的请求的数量*/
threadpool(int thread_number = 8, int max_requests = 10000);
~threadpool();
bool append(T* request);
private:
/*工作线程运行的函数,它不断从工作队列中取出任务并执行之*/
static void* worker(void* arg);
void run();
private:
// 线程的数量
int m_thread_number;
// 描述线程池的数组,大小为m_thread_number
pthread_t * m_threads;
// 请求队列中最多允许的、等待处理的请求的数量
int m_max_requests;
// 请求队列
std::list< T* > m_workqueue;
// 保护请求队列的互斥锁
locker m_queuelocker;
// 是否有任务需要处理
sem m_queuestat;
// 是否结束线程
bool m_stop;
};
template< typename T >
threadpool< T >::threadpool(int thread_number, int max_requests) :
m_thread_number(thread_number), m_max_requests(max_requests),
m_stop(false), m_threads(NULL) {
if((thread_number <= 0) || (max_requests <= 0) ) {
throw std::exception();
}
m_threads = new pthread_t[m_thread_number];
if(!m_threads) {
throw std::exception();
}
// 创建thread_number 个线程,并将他们设置为脱离线程。
for ( int i = 0; i < thread_number; ++i ) {
printf( "create the %dth thread\n", i);
if(pthread_create(m_threads + i, NULL, worker, this ) != 0) {
delete [] m_threads;
throw std::exception();
}
if( pthread_detach( m_threads[i] ) ) {
delete [] m_threads;
throw std::exception();
}
}
}
template< typename T >
threadpool< T >::~threadpool() {
delete [] m_threads;
m_stop = true;
}
template< typename T >
bool threadpool< T >::append( T* request )
{
// 操作工作队列时一定要加锁,因为它被所有线程共享。
m_queuelocker.lock();
if ( m_workqueue.size() > m_max_requests ) {
m_queuelocker.unlock();
return false;
}
m_workqueue.push_back(request);
m_queuelocker.unlock();
m_queuestat.post();
return true;
}
template< typename T >
void* threadpool< T >::worker( void* arg )
{
threadpool* pool = ( threadpool* )arg;
pool->run();
return pool;
}
template< typename T >
void threadpool< T >::run() {
while (!m_stop) {
m_queuestat.wait();
m_queuelocker.lock();
if ( m_workqueue.empty() ) {
m_queuelocker.unlock();
continue;
}
T* request = m_workqueue.front();
m_workqueue.pop_front();
m_queuelocker.unlock();
if ( !request ) {
continue;
}
request->process();
}
}
#endif
locker.h
#ifndef LOCKER_H
#define LOCKER_H
#include <exception>
#include <pthread.h>
#include <semaphore.h>
// 线程同步机制封装类
// 互斥锁类
class locker {
public:
locker() {
if(pthread_mutex_init(&m_mutex, NULL) != 0) {
throw std::exception();
}
}
~locker() {
pthread_mutex_destroy(&m_mutex);
}
bool lock() {
return pthread_mutex_lock(&m_mutex) == 0;
}
bool unlock() {
return pthread_mutex_unlock(&m_mutex) == 0;
}
pthread_mutex_t *get()
{
return &m_mutex;
}
private:
pthread_mutex_t m_mutex;
};
// 条件变量类
class cond {
public:
cond(){
if (pthread_cond_init(&m_cond, NULL) != 0) {
throw std::exception();
}
}
~cond() {
pthread_cond_destroy(&m_cond);
}
/*
条件变量的使用机制需要配合锁来使用
内部会有一次加锁和解锁
封装起来会使得更加简洁
*/
bool wait(pthread_mutex_t *m_mutex) {
int ret = 0;
pthread_mutex_lock(&m_mutex);
ret = pthread_cond_wait(&m_cond, m_mutex);
pthread_mutex_unlock(&m_mutex);
return ret == 0;
}
bool timewait(pthread_mutex_t *m_mutex, struct timespec t) {
int ret = 0;
pthread_mutex_lock(&m_mutex);
ret = pthread_cond_timedwait(&m_cond, m_mutex, &t);
pthread_mutex_unlock(&m_mutex);
return ret == 0;
}
bool signal() {
return pthread_cond_signal(&m_cond) == 0;
}
bool broadcast() {
return pthread_cond_broadcast(&m_cond) == 0;
}
private:
pthread_cond_t m_cond;
};
// 信号量类
class sem {
public:
// 构造函数
sem() {
// 信号量初始化
if( sem_init( &m_sem, 0, 0 ) != 0 ) {
throw std::exception();
}
}
sem(int num) {
if( sem_init( &m_sem, 0, num ) != 0 ) {
throw std::exception();
}
}
// 析构函数
~sem() {
// 信号量销毁
sem_destroy( &m_sem );
}
// 等待信号量
bool wait() {
return sem_wait( &m_sem ) == 0;
}
// 增加信号量
bool post() {
return sem_post( &m_sem ) == 0;
}
private:
sem_t m_sem;
};
#endif