-
业务逻辑线程:
-
和系统线程概念不一样,用户线程和系统线程有1:n、m:n、1:1,Linux和windows一般都用的1:1模型,执行效率块,但有最大线程限制。
-
iocp(windows)启动时就会开启cpu*2+2个线程,这是操作系统的线程,和业务处理(充值、抽卡)无关。
-
- 主线程往消息队列扔包,其他线程从里面取走这个包(互斥)
-
posix线程:标准化的线程标准,说白了就是一堆我们可以调用的函数,一般是以pthread_开头=》posix库并不是Linux默认的库
-
所以编译的时候,makefile要指令 -lpthread
-
-
线程池:
-
结构:消息队列、消息队列计数器、消息队列互斥量
-
线程操作:
-
向消息队列添加包:一个主线程(也要先加互斥锁),要告诉线程池来干活
-
从消息队列拿包:多个子线程,先加互斥锁(也就是初始化互斥量,函数结束后,这个互斥量会被自动析构(析构函数))
-
-
线程池的好处:
- 提升稳定性:避免创建线程失败
- 提升效率:复用
-
- 线程池类:
-
线程池创建在epoll之前,因为epoll来数据了你必须要处理,threadPool_init 和 epoll_init必须加个sleep(1),免得主线程已经初始化了,线程池还没创建完
- 成员:
- 一个结构ThreadItem: =》 和一个线程绑定起来
- 线程句柄 : 需要一块内存啦
- 记录线程池的指针
- 线程池是否正式启动的标志
- 构造和析构函数
- 线程同步互斥量(线程同步锁)
- 线程同步条件变量
- 线程退出标志
- 要创建的线程数量(配置文件中,不建议超过300)、运行的线程数量thread_busy_num(atomic原子操作,多线程里需要)
- 线程容器:vector<ThreadItem *>
- 一个结构ThreadItem: =》 和一个线程绑定起来
-
成员函数:
-
创建线程中所有线程:
-
一个线程的创建:创建一个新线程对象(空间以及这个空间有哪些数据),添加到容器;根据这个线程对象,创建线程。
-
pthread_create:线程对象句柄(pthread_t线程空间),线程入口函数,函数参数
-
创建所有线程后,线程就开始执行入口函数,所有线程需要都卡在一个地方休眠
-
-
精华:
-
达到所有线程都休眠的初始状态:
-
进入while循环(消息队列可获取数据包,且线程池需处于开启状态m_shut=false){pthread_cond_wait(条件变量,释放互斥量)},每创建一个线程,都会休眠,并释放互斥锁,使第二个也能成功休眠,达到所有子线程都成功休眠的初始状态。
-
-
达到所有线程都释放的结束状态:
-
pthread_cond_broadcast(条件变量);以广播的方式唤醒所有持有条件变量的线程。
-
将m_shut=ture,这样所有等待的线程会被唤醒跳出循环(也是一个一个跳出的),没休眠的线程也不会进入循环,会执行执行下面的线程池析构代码。
- 返回一个线程 pthread_join(线程句柄,null),直到所有句柄全部退出,开始销毁线程池(条件变量和互斥量)
- 优雅退出:写在主线程的while循环后,两个while:外面的是while(true){获取消息队列互斥锁;while();执行任务(取包)。。。。}
- 条件变量提供了一个多线程汇合的场所,条件变量+互斥变量可实现线程无竞争执行
- c++ 11就是wait()和notify_one()、notify_all()
-
- 调用线程编程busy:
-
pthread_cond_singal:至少唤醒 一个线程(多核cpu中可能唤醒多个=》虚假唤醒=》惊群),while循环取消息队列数据
-
惊群也不怕,因为只有一个线程能获取互斥锁,另外的线程即使被唤醒,也无法执行outMsgRecvQueue()函数,拿不到数据,将继续进入循环休眠。
-
-
-
- LT发数据:
- 服务器接受到客户端的数据后,向socket(结构体)添加一个可写事件,
- socket可写事件:每个tcp连接都有一个接受和发送缓冲区,一般几十k。
- send() 或 write() 就是把数据放到发送缓存区,然后就返回,客服端内核接收(recv或read)到这些数据后,服务端才会真正把发送缓冲区的数据删掉。此时,如果发送缓存区如果有空间,服务端就可以继续send()了
- 所以,如果服务端发送太快,发送缓冲区就满了。socket可写就是(发送缓存区没满不停触发socket可写事件)
- 不停触发:写日志的话,一下就几百M
- 解决:(并不是很懂。。。)
- send()或write后,把写事件从红黑树中的socket节点移除。事件处理前后都要操作epoll,效率不太高。
- 开始不把写事件加入到epoll,当我需要写数据时,直接调用write/send发送数据;如果发送缓存区已满,就添加到epoll中,再执行上一个方法。
- gdb:
- 首先,编译时g++ 要带 -g选项
- gdb默认调试主进程,gdb7.0可以调试子进程
- b loginc/ngx_c_slogic.cxx:198 打断点
- run 运行到断点
- print 变量值
- c 继续运行
- 信号量:
- 互斥量:线程间的同步
- 信号量:提供进程之间的同步,也能提供线程之间的同步
- 初始化信号量(信号量地址,线程0还是进程同步,信号量初始参数0),用完后释放
- smt_post():
- 将指定信号量值加+1,即便没有其他线程在等待该信号
- smt_wait():
- 测试指定信号量的值,如果值大于0,值-1,返回理解返回
- 如果等于0,将睡眠,直到这个值大于0
- 连接池中连接的回收:
- 如果客户端A断线,服务端立即回收连接,这个连接被B使用
- A的进程执行10s,在第5秒就断了
- A断线,epoll_wait是可以理解感知到的,理解收回A的连接
- 第7s,连接给B了,这个线程并不知道这个连接归属B了;可能往错误的地址写了数据,造成服务器崩溃。
- 所以,连接池中的连接,如果回收了,不能立即加入到空闲链表,而是放到其他地方(延时回收链表),过个60s才放到里面去
- 在niginx里,是一个线程绑定一块内存,解决这个问题(这样就很好了,延时回收很麻烦)
- 两种情况:立即回收(accept没有接入)、延迟回收(已经开始有数据收发了)
- 立即回收:扔到连接池中
- 延迟回收:添加一个延迟回收链表
- 连接的延迟回收会不会使得连接不够用:在延迟回收链表中连接较多时,可以稍微超过最大连接数
- 如果客户端A断线,服务端立即回收连接,这个连接被B使用
-
Linux C++通讯架构【六】:多线程服务业务处理逻辑
于 2022-02-28 20:29:36 首次发布