线程同步机制类封装及线程池实现

>>基础知识

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

​

推荐和参考文章:最新版Web服务器项目详解 - 01 线程同步机制封装类 (qq.com)icon-default.png?t=N7T8https://mp.weixin.qq.com/s?__biz=MzAxNzU2MzcwMw==&mid=2649274278&idx=3&sn=5840ff698e3f963c7855d702e842ec47&chksm=83ffbefeb48837e86fed9754986bca6db364a6fe2e2923549a378e8e5dec6e3cf732cdb198e2&scene=178&cur_album_id=1339230165934882817#rd 

猜你喜欢

转载自blog.csdn.net/weixin_41987016/article/details/132696444
今日推荐