本文借一个小 demo,从内置服务器角度,探讨一下 PHP 与 Go 的区别
PHP
新建一个 index.php 文件
<?php
$request_type=$_GET['type']??0;
$request_name=$_GET['name']??"";
vprintf("请求 %s, 开始时间 %d \r\n",[$request_name,time()]);
if ($request_type == "1") {
sleep(30);
vprintf("请求 %s, 结束时间 %d \r\n",[$request_name,time()]);
} else {
vprintf("请求 %s, 结束时间 %d \r\n",[$request_name,time()]);
}
?>
然后执行如下命令,开启内置 http 服务器
php -S 127.0.0.1:1333
然后依次快速执行如下命令,模拟并发
# PHP由于阻塞,需要开启5个终端窗口分别执行观察效果
curl "127.0.0.1:1333?name=1&type=1"
curl "127.0.0.1:1333?name=2"
curl "127.0.0.1:1333?name=3"
curl "127.0.0.1:1333?name=4"
curl "127.0.0.1:1333?name=5"
得出如下输出:
请求 1, 开始时间 1652149508
请求 1, 结束时间 1652149538
请求 2, 开始时间 1652149538
请求 2, 结束时间 1652149538
请求 3, 开始时间 1652149538
请求 3, 结束时间 1652149538
请求 4, 开始时间 1652149538
请求 4, 结束时间 1652149538
请求 5, 开始时间 1652149538
请求 5, 结束时间 1652149538
由于请求1需要30秒的睡眠时间,导致请求 2、3、4、5进来时,全部都延迟处理了
可见 PHP 的内置服务器,正如官方文档上所说,是单线程的,同一时间,只能处理一个请求
The web server runs only one single-threaded process, so PHP applications will stall if a request is blocked.
翻译一下就是,内置的 web 服务器只运行了一个单线程,如果请求阻塞,PHP 应用就挂掉了
Go
package main
import (
"fmt"
"net/http"
"net/url"
"time"
)
func main() {
http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
u, _ := url.Parse(request.URL.String())
values, _ := url.ParseQuery(u.RawQuery)
request_type := values.Get("type")
request_name := values.Get("name")
fmt.Printf("请求 %s, 开始时间 %d \r\n", request_name, time.Now().Unix())
if request_type == "1" {
time.Sleep(30 * time.Second)
fmt.Printf("请求 %s, 结束时间 %d \r\n", request_name, time.Now().Unix())
} else {
fmt.Printf("请求 %s, 结束时间 %d \r\n", request_name, time.Now().Unix())
}
})
http.ListenAndServe(":1333", nil)
}
同样的测试流程,输出内容如下:
请求 1, 开始时间 1652147934
请求 2, 开始时间 1652147936
请求 2, 结束时间 1652147936
请求 3, 开始时间 1652147937
请求 3, 结束时间 1652147937
请求 4, 开始时间 1652147939
请求 4, 结束时间 1652147939
请求 5, 开始时间 1652147940
请求 5, 结束时间 1652147940
请求 1, 结束时间 1652147964
请求1在睡眠30秒的时候,完全不影响请求2、3、4、5的处理
可见,Go 的内置服务器,是原生支持多线程的
Hyperf
据 Hyperf 官网介绍,Hyperf 是一个高性能、高灵活性的渐进式 PHP 协程框架
class IndexController extends AbstractController
{
public function index()
{
// Hyperf 无法通过 $_GET 获取 URL 参数
$request_type=$this->request->input('type');
$request_name=$this->request->input('name');
vprintf("请求 %s, 开始时间 %d \r\n",[$request_name,time()]);
if ($request_type == "1") {
sleep(30);
vprintf("请求 %s, 结束时间 %d \r\n",[$request_name,time()]);
} else {
vprintf("请求 %s, 结束时间 %d \r\n",[$request_name,time()]);
}
}
}
使用 php bin/hyperf.php start
来启动,同样的测试流程,Hyperf 输出内容如下:
请求 1, 开始时间 1652232037
请求 2, 开始时间 1652232039
请求 2, 结束时间 1652232039
请求 3, 开始时间 1652232040
请求 3, 结束时间 1652232040
请求 4, 开始时间 1652232041
请求 4, 结束时间 1652232041
请求 5, 开始时间 1652232042
请求 5, 结束时间 1652232042
请求 1, 结束时间 1652232067
可见 Hyperf 也是能够实现请求的并发处理的