多进程/线程
最早的服务器端程序都是通过多进程、多线程来解决并发IO的问题。进程模型出现的最早,从Unix 系统诞生就开始有了进程的概念。最早的服务器端程序一般都是 Accept 一个客户端连接就创建一个进程,然后子进程进入循环同步阻塞地与客户端连接进行交互,收发处理数据。
多线程模式出现要晚一些,线程与进程相比更轻量,而且线程之间共享内存堆栈,所以不同的线程之间交互非常容易实现。比如实现一个聊天室,客户端连接之间可以交互,聊天室中的玩家可以任意的其他人发消息。用多线程模式实现非常简单,线程中可以直接向某一个客户端连接发送数据。而多进程模式就要用到管道、消息队列、共享内存等等统称进程间通信(IPC)复杂的技术才能实现。
最简单的多进程服务端模型
$serv = stream\_socket\_server("tcp://0.0.0.0:8000", $errno, $errstr) or die("Create server failed");
while(1) {
$conn = stream\_socket\_accept($serv);
if (pcntl_fork() == 0) {
$request = fread($conn); // do something
// $response = "hello world";
fwrite($response);
fclose($conn);
exit(0);
}
}
多进程/线程模型的流程是:
创建一个 socket
,绑定服务器端口(bind
),监听端口(listen
),在 PHP 中用 stream_socket_server
一个函数就能完成上面 3 个步骤,当然也可以使用更底层的sockets
扩展分别实现。
进入 while
循环,阻塞在 accept
操作上,等待客户端连接进入。此时程序会进入随眠状态,直到有新的客户端发起 connect
到服务器,操作系统会唤醒此进程。accept
函数返回客户端连接的 socket
主进程在多进程模型下通过 fork
(php: pcntl_fork)创建子进程,多线程模型下使用 pthread_create
(php: new Thread)创建子线程。
下文如无特殊声明将使用进程同时表示进程/线程。
子进程创建成功后进入 while
循环,阻塞在 recv
(php:fread)调用上,等待客户端向服务器发送数据。收到数据后服务器程序进行处理然后使用 send
(php: fwrite)向客户端发送响应。长连接的服务会持续与客户端交互,而短连接服务一般收到响应就会 close
。
当客户端连接关闭时,子进程退出并销毁所有资源,主进程会回收掉此子进程。
这种模式最大的问题是,进程创建和销毁的开销很大。所以上面的模式没办法应用于非常繁忙的服务器程序。对应的改进版解决了此问题,这就是经典的 Leader-Follower
模型。
$serv = stream\_socket\_server("tcp://0.0.0.0:8000", $errno, $errstr)
or die("Create server failed");
for($i = 0; $i < 32; $i++) {
if (pcntl_fork() == 0) {
while(1) {
$conn = stream\_socket\_accept($serv);
if ($conn == false) continue; // do something
$request = fread($conn); // $response = "hello world";
fwrite($response);
fclose($conn);
}
exit(0);
}
}
它的特点是程序启动后就会创建 N 个进程。每个子进程进入 Accept
,等待新的连接进入。当客户端连接到服务器时,其中一个子进程会被唤醒,开始处理客户端请求,并且不再接受新的 TCP 连接。当此连接关闭时,子进程会释放,重新进入 Accept
,参与处理新的连接。
这个模型的优势是完全可以复用进程,没有额外消耗,性能非常好。很多常见的服务器程序都是基于此模型的,比如 Apache、PHP-FPM。
多进程模型也有一些缺点。
这种模型严重依赖进程的数量解决并发问题,一个客户端连接就需要占用一个进程,工作进程的数量有多少,并发处理能力就有多少。操作系统可以创建的进程数量是有限的。
启动大量进程会带来额外的进程调度消耗。数百个进程时可能进程上下文切换调度消耗占 CPU 不到1%可以忽略不接,如果启动数千甚至数万个进程,消耗就会直线上升。调度消耗可能占到 CPU 的百分之几十甚至 100%。
并行和并发
谈到多进程以及类似同时执行多个任务的模型,就不得不先谈谈并行和并发。
并发(Concurrency)
是指能处理多个同时性活动的能力,并发事件之间不一定要同一时刻发生。
并行(Parallesim)
是指同时发生的两个并发事件,具有并发的含义,而并发则不一定并行。
区别
-
『并发』指的是程序的结构,『并行』指的是程序运行时的状态
-
『并行』一定是并发的,『并行』是『并发』设计的一种
-
单线程永远无法达到『并行』状态
正确的并发设计的标准是:
使多个操作可以在重叠的时间段内进行。 two tasks can start, run, and complete in overlapping time periods