14业务逻辑-线程池处理消息队列

一、线程池处理业务逻辑的必要性

业务逻辑层面,这个就要用多线程处理,所谓业务逻辑:充值,抽卡,战斗;
对于充值,需要本服务器和专门的充值服务器通讯,一般需要数秒到数十秒的通讯时间。此时,必须采用多线程【100个多线程】处理方式,保证用户的需求能够得到及时处理
线程池好处:
提前创建好一堆线程,并搞一个类来统一管理和调度这一堆线程【这一堆线程我们就叫做线程池】
当来了一个任务【来了一个消息】的时候,我从这一堆线程中找一个空闲的线程去做这个任务【去干活/去处理这个消息】,活干完之后,我这个线程里边有一个循环语句,我可以循环回来等待新任务,再有新任务的时候再去执行新的任务;就好像这个线程可以回收再利用 一样。
----a)实现创建好一堆线程,避免动态创建线程来执行任务,提高了程序的稳定性;有效的规避程序运行之中创建线程有可能失败的风险;
----b)提高程序运行效率:线程池中的线程,反复循环再利用;

二、线程池结构

2.1线程池的初始化

线程池类对象的实例化(线程池的管理者):

CThreadPool     g_threadpool;           //线程池全局对象
-------------------------------
class CThreadPool
{
public:
    //构造函数
    CThreadPool();               
    //析构函数
    ~CThreadPool();                           
public:
    bool Create(int threadNum);                 //创建该线程池中的所有线程
    void StopAll();                             //使线程池中的所有线程退出
    void Call(int irmqc);                       //来任务了,调一个线程池中的线程下来干活
private:
    static void* ThreadFunc(void *threadData);  //新线程的线程回调函数,静态函数是属于类的
private:
    //定义一个 线程池中的 线程 的结构,以后可能做一些统计之类的 功能扩展,所以引入这么个结构来 代表线程 感觉更方便一些; 
    //线程有关的信息  ======================================    一条线程的信息结构
    struct ThreadItem   
    {
        pthread_t   _Handle;                              //线程句柄
        CThreadPool *_pThis;                              //记录线程池的指针	================???未完全理解
        bool        ifrunning;                            //标记是否正式启动起来,启动起来后,才允许调用StopAll()来释放
        //构造函数
        ThreadItem(CThreadPool *pthis):_pThis(pthis),ifrunning(false){}                             
        //析构函数
        ~ThreadItem(){}        
    };
private:
    static pthread_mutex_t     m_pthreadMutex;      //线程同步互斥量/也叫线程同步锁
    static pthread_cond_t      m_pthreadCond;       //线程同步条件变量
    static bool                m_shutdown;          //线程退出标志,false不退出,true退出
    int                        m_iThreadNum;        //要创建的线程数量
    //int                        m_iRunningThreadNum; //线程数, 运行中的线程数	
    std::atomic<int>           m_iRunningThreadNum; //线程数, 运行中的线程数,原子操作
    time_t                     m_iLastEmgTime;      //上次发生线程不够用【紧急事件】的时间,防止日志报的太频繁
    //time_t                     m_iPrintInfoTime;    //打印信息的一个间隔时间,我准备10秒打印出一些信息供参考和调试
    //time_t                     m_iCurrTime;         //当前时间
    std::vector<ThreadItem *>  m_threadVector;      //线程 容器,容器里就是各个线程了 
};

线程池的创建和初始化等待:

bool CThreadPool::Create(int threadNum)
它的被调用位置:`ngx_worker_process_init(int inum)`子进程创建时的初始化
//在Create()中有pthread_create()函数
err = pthread_create(&pNew->_Handle, NULL, ThreadFunc, pNew);
//线程是循环创建的,创建完立即执行ThreadFunc(),线程入口函数

PS:细节,在创建inum个线程时,必须保证所有线程都进入线程等待的状态,才能够让(主线程)Create()返回。判断方法:(*iter)->ifrunning

void* CThreadPool::ThreadFunc(void* threadData) 
//有两层while循环,外面一层是while(true)控制线程池的线程执行完业务之后的循环利用,而里面一层的是为了辅助线程等待和取消息队列的作用。
{
    ThreadItem *pThread = static_cast<ThreadItem*>(threadData);
    CThreadPool *pThreadPoolObj = pThread->_pThis;

    char *jobbuf = NULL;    
    CMemory *p_memory = CMemory::GetInstance();	    
    int err;

    pthread_t tid = pthread_self();
    while(true)
    {
        err = pthread_mutex_lock(&m_pthreadMutex);  
        if(err != 0) ngx_log_stderr(err,"CThreadPool::ThreadFunc()pthread_mutex_lock()失败,返回的错误码为%d!",err);
        while( (jobbuf = g_socket.outMsgRecvQueue()) == NULL && m_shutdown == false)
        {
            if(pThread->ifrunning == false)            
                pThread->ifrunning = true; 
            pthread_cond_wait(&m_pthreadCond, &m_pthreadMutex); //整个服务器程序刚初始化的时候,所有线程必然是卡在这里等待的;
        }
        err = pthread_mutex_unlock(&m_pthreadMutex); 
        if(err != 0)  ngx_log_stderr(err,"CThreadPool::ThreadFunc()pthread_cond_wait()失败,返回的错误码为%d!",err);
        //先判断线程退出这个条件
        if(m_shutdown)
        {            
            if(jobbuf != NULL)
            {
                p_memory->FreeMemory(jobbuf);   
            }
            break; 
        }
        ++pThreadPoolObj->m_iRunningThreadNum; 
ngx_log_stderr(0,"执行开始---begin,tid=%ui!",tid);
sleep(5); //临时测试代码
ngx_log_stderr(0,"执行结束---end,tid=%ui!",tid);
        p_memory->FreeMemory(jobbuf);             
        --pThreadPoolObj->m_iRunningThreadNum;  
    } //end while(true)
    return (void*)0;
}

要点解释:
1.在消息队列中没有消息时,所有线程都在pthread_cond_wait(&m_pthreadCond, &m_pthreadMutex);的位置等待。
2.pthread_cond_wait(),在等待的过程中,会放开互斥锁,给其他线程,当收到pthread_cond_signal(&m_pthreadCond);条件变量的激活时,会继续执行pthread_cond_wait后面的代码,不过在此之前。原先放开的互斥锁有重新锁住【pthread_cond_wait()返回时,互斥量再次被锁住】。
3.调用一次pthread_cond_signal()可能会唤醒多个在pthread_cond_wait()等待的线程【惊群】,互斥量再次被锁住这是第一道墙,还有就是内层的while( (jobbuf = g_socket.outMsgRecvQueue()) == NULL && m_shutdown == false),会再一次判断,只有没取到消息,那么就在待到内层的等待中,只有取到的消息,才能够跳出内层的while。
4.跳出了内层的循环之后,就可以执行业务逻辑的工作。

2.2线程池的激发

void CSocekt::ngx_wait_request_handler_proc_plast(lpngx_connection_t c)//包的最后阶段处理
----void CThreadPool::Call(int irmqc)//"激发"线程池中的某个线程来处理业务逻辑,让线程池开始做事了
--------int err = pthread_cond_signal(&m_pthreadCond);//条件变量的激发函数
posted @ 2022-03-11 16:16  豪崽_ZH  阅读(408)  评论(0)    收藏  举报