(1)学习方法
学习解决问题的思路:多学多思考
(2)多线程的提出
①上次5.9收到数据包,调用ngx_wait_handler_proc_p1收到数据包,然后调用ngx_wait_request_handler_proc_plast把数据包扔到消息队列inMsgRecvQueue中来,接下来解析数据包,处理结果《《–用线程来处理问题
②一个进程 跑起来之后缺省 就自动启动了一个 “主线程”,也就是我们一个worker进程一启动就等于只有一个“主线程”在跑;我们现在涉及到了业务逻辑层面,这个就要用多线程处理,所谓业务逻辑:充值,抽卡,战斗;
1)例如:充值,需要本服务器和专门的充值服务器通讯,一般需要数秒到数十秒的通讯时间。此时,我们必须采用多线程【100个多线程】处理方式;
2)一个线程因为充值被卡住,还有其他线程可以提供给其他玩家及时的服务(这里线程于iocp没关系,只处理逻辑);
(iocp(windows)启动线程数为cpu*2+2;)
3)互斥:
a)主线程 往消息队列中用inMsgRecvQueue()扔完整包(用户需求),那么一堆线程要从这个消息对列中取走这个包,所在必须要用互斥;
b)引入变量m_recvMessageQ,定义消息队列互斥量
1))在ngx_c_socket.cxx构造类中初始化互斥量
2))在ngx_c_socket.cxx析构函数中销毁
3))在ngx_c_socket_requests.cxx中的inMsgRecvQueue()启动CLock(实现自动加锁解锁,在ngx_c_lockmutex.h中)
自动加锁解锁,irmqc把消息队列的数字返回去
4))outMsgRecvQueue()从消息队列中取数据,这个函数返回的是字符串
(一个主线程放消息,多个子线程从消息队列中取消息)
4)多线程名词
a)POSIX:表示可移植操作系统接口(Portable Operating System Interface of UNIX)。
b)POSIX线程:是POSIX的线程标准【大概在1995年左右标准化的】;它定义了创建和操纵线程的一套API(Application Programming Interface:应用程序编程接口),说白了 定义了一堆我们可以调用的函数,一般是以pthread_开头,比较成熟,比较好用;我们就用这个线程标准;
c)现在是NPTL线程
(3)线程池实战代码
(3.1)为什么引入线程池
1)线程池:说白了 就是 我们提前创建好一堆线程,并搞一个雷来统一管理和调度这一堆线程【这一堆线程我们就叫做线程池】,
①当来了一个任务【来了一个消息】的时候,我从这一堆线程中找一个空闲的线程去做这个任务【去干活/去处理这个消息】,
②活干完之后,我这个线程里边有一个循环语句,我可以循环回来等待新任务,再有新任务的时候再去执行新的任务;
③就好像这个线程可以回收再利用 一样;
2)线程池存在意义和价值;
①实现创建好一堆线程,避免动态创建线程来执行任务,提高了程序的稳定性;有效的规避程序运行之中创建线程有可能失败的风险;
②提高程序运行效率:线程池中的线程,反复循环再利用;
但是说到根上,用线程池的目的无非就两条:提高稳定性,提升整个程序运行效率,容易管理【使编码更清晰简单】
(4)线程池的使用
1)引入misc中的新文件ngx_c_threadpool.cxx建立线程池和头文件ngx_c_threadpool.h
还要在common.mk中72行加上-lpthread
2)ngx_c_threadpool.h有一个类叫CTreadPool
Create创建线程池
StopAll()使线程池所有线程退出
Call()来任务了,调用线程池的一个线程出来工作
3)在类中定义了类的结构
4)配置文件nginx.conf中配置线程池数量
5)bool CTreadPool线程池管理类:用来管理线程池
6)线程池的线程
为了避免线程池的异常,所有线程必须都卡在pthread_cond_wait(&m_pthreadCond,&mpthreadMutex);才认为线程启动起来,这个函数会释放互斥量,都会卡在这
7)静态成员函数,是不存在this指针的
8)线程池精华代码
从消息队列中拿出一个请求,且系统不是关闭状态的
(4.1)线程池的初始化
1)在nginx.cxx中定义了线程池的全局对象
2)
①子线程的for循环在ngx_worker_process_init()中初始化线程池,
②然后读配置文件中线程池的数量,
③之后触发epoll的初始化,epoll触发就来事件了(得来事件之前创建号线程池)
④补充上线程池创建失败就退出
⑤再等待sleep1秒,让所有线程都卡在pthread_cond_wait()(前面有提到)
⑥在worker进程的循环创建线程的for之外用g_threadpool.StopAll()终止线程池,释放所有线程,用brocast激活所有卡在pthread_cond_wait()的线程并都退出,
用for循环去等线程退出,一个个pthread_join掉线程的句柄
⑦释放锁和条件变量,释放new出来的线程
(4.2)线程池工作的激发
1)ngx_wait_request_handler_proc_plast()函数的inMsgRecvQueue()中来一个数据包,接下来我就激发线程池的某个线程来处理业务逻辑(irmqc为消息队列中的消息数)
2)用pthread_cond_signal激发某一个线程
3)单一线程线程被激活开始干活
补充:call函数主体
//来任务了,调一个线程池中的线程下来干活
void CThreadPool::Call(int irmqc)
{
//ngx_log_stderr(0,"m_pthreadCondbegin--------------=%ui!",m_pthreadCond); //数字5,此数字不靠谱
//for(int i = 0; i <= 100; i++)
//{
int err = pthread_cond_signal(&m_pthreadCond); //唤醒一个等待该条件的线程,也就是可以唤醒卡在pthread_cond_wait()的线程
if(err != 0 )
{
//这是有问题啊,要打印日志啊
ngx_log_stderr(err,"CThreadPool::Call()中pthread_cond_signal()失败,返回的错误码为%d!",err);
}
//}
//唤醒完100次,试试打印下m_pthreadCond值;
//ngx_log_stderr(0,"m_pthreadCondend--------------=%ui!",m_pthreadCond); //数字1
//(1)如果当前的工作线程全部都忙,则要报警
//bool ifallthreadbusy = false;
if(m_iThreadNum == m_iRunningThreadNum) //线程池中线程总量,跟当前正在干活的线程数量一样,说明所有线程都忙碌起来,线程不够用了
{
//线程不够用了
//ifallthreadbusy = true;
time_t currtime = time(NULL);
if(currtime - m_iLastEmgTime > 10) //最少间隔10秒钟才报一次线程池中线程不够用的问题;
{
//两次报告之间的间隔必须超过10秒,不然如果一直出现当前工作线程全忙,但频繁报告日志也够烦的
m_iLastEmgTime = currtime; //更新时间
//写日志,通知这种紧急情况给用户,用户要考虑增加线程池中线程数量了
ngx_log_stderr(0,"CThreadPool::Call()中发现线程池中当前空闲线程数量为0,要考虑扩容线程池了!");
}
} //end if
/*
//-------------------------------------------------------如下内容都是一些测试代码;
//唤醒丢失?--------------------------------------------------------------------------
//(2)整个工程中,只在一个线程(主线程)中调用了Call,所以不存在多个线程调用Call的情形。
if(ifallthreadbusy == false)
{
//有空闲线程 ,有没有可能我这里调用 pthread_cond_signal(),但因为某个时刻线程曾经全忙过,导致本次调用 pthread_cond_signal()并没有激发某个线程的pthread_cond_wait()执行呢?
//我认为这种可能性不排除,这叫 唤醒丢失。如果真出现这种问题,我们如何弥补?
if(irmqc > 5) //我随便来个数字比如给个5吧
{
//如果有空闲线程,并且 接收消息队列中超过5条信息没有被处理,则我总感觉可能真的是 唤醒丢失
//唤醒如果真丢失,我是否考虑这里多唤醒一次?以尝试逐渐补偿回丢失的唤醒?此法是否可行,我尚不可知,我打印一条日志【其实后来仔细相同:唤醒如果真丢失,也无所谓,因为ThreadFunc()会一直处理直到整个消息队列为空】
ngx_log_stderr(0,"CThreadPool::Call()中感觉有唤醒丢失发生,irmqc = %d!",irmqc);
int err = pthread_cond_signal(&m_pthreadCond); //唤醒一个等待该条件的线程,也就是可以唤醒卡在pthread_cond_wait()的线程
if(err != 0 )
{
//这是有问题啊,要打印日志啊
ngx_log_stderr(err,"CThreadPool::Call()中pthread_cond_signal 2()失败,返回的错误码为%d!",err);
}
}
} //end if
//(3)准备打印一些参考信息【10秒打印一次】,当然是有触发本函数的情况下才行
m_iCurrTime = time(NULL);
if(m_iCurrTime - m_iPrintInfoTime > 10)
{
m_iPrintInfoTime = m_iCurrTime;
int irunn = m_iRunningThreadNum;
ngx_log_stderr(0,"信息:当前消息队列中的消息数为%d,整个线程池中线程数量为%d,正在运行的线程数量为 = %d!",irmqc,m_iThreadNum,irunn); //正常消息,三个数字为 1,X,0
}
*/
return;
}
//唤醒丢失问题,sem_t sem_write;
//参考信号量解决方案:https://blog.csdn.net/yusiguyuan/article/details/20215591 linux多线程编程--信号量和条件变量 唤醒丢失事件
(4.3)线程池完善和测试
a)我只开一个线程【线程中暂时测只有一个线程】,需要验证知否遗漏消息–》》
添加C++11中原子整型数量,比互斥量要快(头文件添加atomic,C++中原子操作),有数据处理+1,无数据处理要减1
common.mk修改加上-std-c++11
gcc改成(CC)
若线程池全被调用了,要么减少连入的客户端数量,要么增加线程数,10秒报一下错误
b)惊群(也叫虚假唤醒):处于cond_wait状态的线程,收到call的signal函数会激活多个线程。
但是: 不用担心,一般都会设置拿到消息队列的数据才会跳出循环开始执行,没拿到消息队列的数据还是处于循环当中
c)前面a)中提到的多个消息用了原子操作后会堆积,但不会丢消息,消息会逐条处理