1.动态创建进程和线程缺点:
1.动态创建进程(或线程)比较耗费时间
2.动态创建的子进程(或子线程)通常只用来为一个客户服务,这将导致系统上产生大量的细微进程(或线程)。
进程(或线程)间的切换将消耗大量CPU时间。
3.动态创建的子进程是当前进程的完整映像。当前进程必须谨慎的管理其分配的文件描述符和堆内存等系统资源。
否则子进程可能复制这些资源,从而使系统的可用资源急剧下降,进而影响服务器的性能。
2.进程池和线程池
进程池(线程池)是由服务器预先创建的一组子进程,这些子进程的数目在 3~10 个之间(典型情况)。
进程池中的所有子进程都运行着相同的代码,并具有相同的属性,比如优先级,PGID等。
因为进程池在服务器启动之初就创建好了,所有每个子进程都相对"干净",即它们没有打开不必要的文件描述符(从父进程继承而来),
也不会错误的使用大块的堆内存(从父进程复制而来)。
当有新任务到来时,主进程将通过某种方式选择进程池中的某一个子进程来为之服务。相比于动态创建子进程,
选择一个已经存在的子进程的代价显然要小的多。主进程选择子进程的方式有2种:
1.主进程使用某种算法来主动选择子进程。(随机算法,Round Robin ...)
2.主进程和所有子进程通过一个共享的工作队列来同步,子进程都睡眠在该工作队列上。当有新任务到来时,主进程将任务添加到工作队列中。
这将唤醒正在等待任务的子进程,不过只有一个子进程将获得新任务的"接管权",它可以从工作队列中取出任务并执行之,而其他子进程将继续
睡眠在工作队列上。
当选择好子进程后,主进程还需要使用某种通知机制来告诉目标子进程有新任务需要处理,并传递必要的数据。最简单的方法是,在父进程和子进程之间
预先建立一条管道,然后通过该管道来实现所有的进程间通信。在父线程和子线程之间传递数据就比较简单,因为我们会把这些数据定义为全局的,
那么它们本身就是被所有的线程共享的。
3.处理多用户
监听 socket 和 连接 socket 是否都由主进程来统一管理 ?
半同步/半反应堆 都是由主进程统一管理者两种 socket :
主进程接受新的连接以得到 连接 socket, 然后它需要将该 socket 传递给子进程(对于线程池而言,父线程将 socket 传递给子线程是很简单的,
因为它们可以很容易的共享该 socket。但对于进程池而言,我们必须使用 13.9 节介绍的方法来传递该 socket)
高效的 半同步/半异步 模式,以及 领导者/追随者 模式都是由主进程管理所有监听 socket, 而各个子进程分别管理属于自己的 连接 socket:
子进程可以自己调用 accept 来接受新的连接,这样父进程就无需向子进程传递 socket, 而只需要简单的通知一声:"我检测到新的连接,你来接受它"
长连接,即一个客户的多次请求可以服用一个 TCP 连接。那么设计进程池时还需要考虑:一个客户连接上的所有任务是否始终由一个子进程来处理。
如果客户任务是无状态的,那么我们可以考虑使用不同子进程来为该客户的不同请求服务。但如果客户任务是存在上下文关系的,则最好一直用一个子进程来为之
服务,否则实现起来比较麻烦,因为我们不得不在各个子进程之间传递上下文数据。 epoll 的 EPOLLONESHOT 事件,这一事件能够确保一个用户连接在整个
生命周期中仅被一个线程处理。
15.1 进程池和线程池概述
15.2 处理多客户
15.3 半同步/半异步进程池的实现
15.4 用进程池实现的简单CGI服务器
15.5 半同步/半反应堆线程池实现
15.6 用线程池实现的简单 Web 服务器