网络数据的处理流程:
1. 检测IO事件是否就绪(可读可写) epoll 等多路复用
2.对IO进行读写 recv/send
3.对数据进程解析与操作 parser()
池式结构:1.连接池 2.线程池 3.内存池 4.异步请求池
为什么需要线程池?在哪些情况下我们会使用到线程池?
阻塞调用(阻塞IO调用、等待资源);耗时的计算(读写文件、复杂的计算);高密度任务(高并发低延时的网络IO请求)
面临以上情况时都去临时创建线程,这样的问题:
创建了太多的线程,系统资源就会被浪费,而且会浪费时间去创建和销毁线程:创建线程太慢,导致执行任务结果返回过慢;销毁线程太慢,可能会影响别的进程使用资源。
所以:创建多个线程,放在池子(管理组件)里不销毁,用的时候就把任务丢给池子里的线程去执行,这就是线程池。那么问题来了,任务由谁产生(生产者),如何丢给线程池的某个线程(消费者)?这问题需要从一下几个方面:
1) 生产者采用什么方式与消费者同步? 2) 任务如何保存? 3) 生产者之间的同步方式,消费者之间的同步方式?
线程池的尺寸设计多大合适?
CPU 密集型的: thread size = N + 1;
IO 密集型的: thread size = 2*N + 1;
当然这不是绝对的,所以在 mariadb 的线程池是可以动态调整这个尺寸的。
线程池:不需要在每次创建线程,避免了线程频繁的创建和销毁
线程池分为三部分: 执行队列(线程) 任务队列(任务) 管理组件
作用: IO 处理的时候 日志处理可以使用线程池
线程共享的环境包括: 进程代码段、进程的公有数据、进程打开的文件描述符,信号处理器、进程的当前目录和进程用户ID 和进程组 ID
每个线程私有的资源包括: 线程 id、寄存器组的值、线程的栈、 errno、线程的信号屏蔽码、线程的优先级
调度实体:使用PCB 进程控制块描述线程实际上也是使用pcb 描述,只是 pcb 的某些变量分别由进程还是线程处理进程是资源分配的最小单元,线程是任务调度的最小单元
多进程:进程的创建需要分配虚拟地址空间、页表、物理内存等;
info thread 查看线程信息 b dothing 跳转到某个函数 bt 查看调用堆栈
b lineno 打断点 c 继续执行
1. 线程不是无限制创建的,而且线程切换是消耗 CPU 资源的
2. 线程的创建和销毁消耗 CPU 资源
3.线程池数量:线程池、 IO 密集型的: thread size = 2*N + 1;一般为 2*cpu 数量
以下是简单版线程池:(我在网上看到一个c的写的线程池:https://github.com/Pithikos/C-Thread-Pool 看到gitbub的一个线程池)
线程池数据结构:

执行队列:

向线程池里抛任务:


在进入任务调度之前空闲数量 = 总的线程数量
线程空闲状态统计,占用总的线程个数的比例,进行统计,对线程池增加和释放线程
100 个任务 5 个线程之间来回切换已经能够处理的,那么就不需要创建 10 个线程
问题:
a. 多个线程同时在一个任务队列中取任务? 加锁,取任务是临界资源
b. 当任务不足时,线程的状态? 条件变量 condition, 条件等待,队列不为空时。
int nready = epoll_wait();
for(i=0;i<nready;i++){
#if 0
a. recv() ; parser(); send() --a--纯内存操作是OK的
#elif 0
b. task = fd; --b--如果fd操作的花费时间长的话,可以这样操作,但是b有一个致命的问题:多线程公用一个fd的现象。可能一个线程正在准备数据,另外一个线程close(fd),脏数据等问题。解法:可以参照协程的做法
push_task(task); //抛给线程、池等
#else
c recv(fd, buffer, sizeof(buffer)); push_task(buffer) --c--业务比较复杂的话使用
#endif
}
检测IO: 可以放在多个线程,epoll_wait() 放在多个线程
读写IO: 可以放在多个线程
解析数据:可以放在多个线程
小结:
线程池说白了,就是提前分配好工人,然后将任务放到任务队列中,让所有的工人去干活。如果要在干活的时候在去创建线程,销毁线程,在创建和销毁的时候是很消耗资源的。将线程提前做好,然后统一销毁就提高了效率。
网友评论