第十五章 进程池与线程池
15.1 进程池和线程池概述
进程池是由服务器预先创建的一组子进程,线程池中的线程数量应该和CPU数量差不多.
- 进程池中的所有子进程都运行着相同的代码,并具有相同的属性,比如优先级,PGID等等。因为进程池在服务器启动之初就创建好了,所以每个子进程都相对“干净”,即他们没有打开不必要的文件描述符(从父进程继承而来),也不会错误地使用大块的堆内存(从父进程复制得到)
15.2 进程池内的进程如何被唤醒
当有新的任务到来时,主进程将通过某种方式选择进程池中的某一个子进程来为之服务.
-
主进程使用某种算法主动选择子进程。最简单、最常用的算法是随机算法和Round-Robin(轮流选取)算法,但更优秀、更智能的算法将使任务在各个工作进程中更均匀地分配、从而减轻服务器的整体压力。
-
主进程和所有子进程通过一个共享的工作队列来同步,子进程都睡眠在该工作队列上。当有心的任务到来时,主进程将任务添加到工作队列中。这将唤醒正在等待任务的子进程,不过只有一个子进程获得新任务的“接管权”,它可以从工作队列中取出并执行之,而其他子进程将继续睡眠在工作队列上。
觉得这个过程有点类似于CPU的调度.
15.3 进程池的实现
这一章之前有一个问题困扰着我,就是究竟如何创建一个进程池,如何回收.
//创建process_number个子进程,并建立它们和父进程之间的管道
for(int i = 0; i < process_number; i++)
{
int ret = socketpair(PF_UNIX, SOCK_STREAM, 0, m_sub_process[i].m_pipefd);
assert(ret == 0);
m_sub_process[i].m_pid = fork();
assert(m_sub_process[i].m_pid >= 0);
// 主进程关闭写
if (m_sub_process[i].m_pid > 0)
{
close(m_sub_process[i].m_pipefd[1]);
continue;
}
// 子进程关闭读
else
{
close(m_sub_process[i].m_pipefd[0]);
m_idx = i;
break;
}
//这就意味着子进程要发送给主进程信号
}
...
for(int i = 0; i < number; i ++)
{
int sockfd = events[i].data.fd;
if (sockfd == m_listenfd)
{
//如果有新连接到来,就采用Round
//Robin方式将其分配给一个子进程处理
int i = sub_process_counter;
do
{
if (m_sub_process[i].m_pid != -1)
{
break;
}
i = (i + 1)%m_process_number;
}
while(i != sub_process_counter);
if (m_sub_process[i].m_pid == -1)
{
m_stop = true;
break;
}
sub_process_counter = (i + 1)%m_process_number;
send(m_sub_process[i].m_pipefd[0],
(char *)&new_conn, sizeof(new_conn), 0);
printf("send request to child %d
", i);
}
// 处理父进程收到的信号,对子进程的行为做出判断和抉择.
// 如若关闭了就回收它.若全部退出了,则主进程也退出.
// ...
}
关于第十五章的总结
- 不深入的了解了进程/线程池的具体实现.
From
Aaron-z/linux-server-high-performance
2017/2/13 15:09:45