大家好,我是yangyang,刚刚看到swoole官方有重要更新,赶紧来跟pai huang pian的同学同步这个好消息
新鲜信息
在 PHP + Swoole 的服务器编程开发中,协程的支持已经解决了大部分难题,但是我们发现跨进程读写数据依然很难,需要借助进程间通信(IPC)、Redis、Swoole\Table 或其他共享内存实现。
Redis、IPC 进程间通信方式性能较差。而 Swoole\Table 的问题是需要固定分配内存,无法扩容,存在诸多限制。
除此之外,多进程的调试非常麻烦,例如我们要使用 gdb 就需要 gdb -p 逐个进程去追踪,而 Java 、Golang 这样的多线程模型,只有一个进程,调试更简单。实现一些底层的工具也会更容易。
为了解决这个问题,Swoole v6 版本将推出一个重磅的更新,引入多线程+协程的编程模式支持。既可以拥有协程并发 IO 编程的便利,又可以拥有多线程并行执行、共享内存堆栈的优势。这使得 PHP 在开发游戏服务器、通信服务器方面更有效率。
实现原理
基于 PHP 的 ZTS 机制和 TSRM API,PHP 层面隔离所有全局变量,实现线程安全。不同于 Python 的伪多线程,Swoole v6 的多线程是真正的多线程实现,所有 PHP 程序代码均是在多核并行执行。
Swoole v6 提供了线程安全的Map 和 ArrayList 数据结构,可实现跨线程的数据共享读写 。
Server 端的 Event Worker、Task Worker、User Process 将全部替换为 Thread,在同一个进程空间内执行。
创建线程
由于 ZTS 的机制,实际上 Swoole\Thread 与 Swoole\Process 是一致的,无法共享任何对象资源。实际上 Thread::exec() 与 Process::exec() 更接近,ZTS 线程反而比 fork() 隔离得更为干净,fork() 是可以从父进程继承已创建的对象和资源,而 ZTS 新的线程不会从父线程继承任何资源,相当于是一个全新的进程。
虽然通过底层的技术手段可以实现线程之间传递对象和资源,例如 ext-pthreads 等扩展,但涉及到并行操作同一个文件句柄和内存指针等复杂的问题。再加上 Swoole 的异步 IO 和协程机制带来的复杂性。应用层代码正确地使用锁,同时兼顾性能和数据一致性是一件极其困难的事情,错误的使用方法导致严重的 BUG,因此 Swoole 不考虑提供这方面的支持。
在线程中创建协程
# index.php
Co\run(function () {
echo "begin\n";
sleep(10);
echo "end\n";
});
在线程中可以使用 Co\run 创建新的协程调度器,使用 Co\go 创建新的协程。不同线程之间的协程无任何关联,包括 Channel 也只能在当前线程中使用。
使用 ps aux 可以看到只有一个进程:
ps aux|grep php
swoole 783550 0.2 0.0 17142
并发 Map
use Swoole\Thread;
use Swoole\Thread\Map;
$map = new Map;
# 写入$map[time()] = 'value';
$map['hello'] = 3.1415926;
# 读取echo $map['hello'];
# 删除unset($map['hello']);
# 获取长度count($map);
# 获取所有 Key ,
若 Map 过大可能会长时间占用锁,
导致其他线程全部阻塞,
建议只在 `shutdown` 阶段使用$map->keys();
并发 ArrayList
use Swoole\Thread;
use Swoole\Thread\ArrayList;
$list = new ArrayList();
# 追加元素$list[] = time();
$list[] = 99999;
$list[2] = 'test';
# 获取长度count($list);
# 抛出异常 unsupported behavior,不支持随机删除unset($list[1]);
# 赋值$list[0] = 0;
# 抛出 out of range 异常,错误的赋值$list[1000] = 0;
线程安全
- Map 和 ArrayList 在同一个进程的内存堆栈中,因此可以分配内存自由伸缩,不需要像 Table 那样固定分配
- 底层会自动加锁,是线程安全的
- 仅支持 null/bool/int/float/string 类型,其他类型将在写入时自动序列化,读取时反序列化
- 不支持迭代器,在迭代器中删除元素会出现内存错误
- 必须在线程创建前将 Map 和 ArrayList 对象作为线程参数传递给子线程
$list = new Swoole\Thread\ArrayList();
$list[] = base64_encode(random_bytes(32));
$list[1] = uniqid();
$t1 = Swoole\Thread::exec('mt.php', 'thread-1', $list);
多线程缺点
- 发生 Crash 时或调用了 Process::exit() 整个进程都会退出,需要在客户端做好错误重试、断线重连等故障恢复逻辑,另外需要使用 supervisor 和 docker/k8s 在进程退出后自动重启
- ZTS 和 锁的操作可能会额外的开销,性能可能会比 NTS 多进程并发模型差 10% 左右,如果是无状态的服务,仍建议使用 NTS 多进程的运行方式
其他线程 API
- Thread::getId():获取当前线程的 ID
- Thread::getArguments():获取父线程传递给子线程的参数列表
- Thread::join() 等待子线程退出,请注意 $thread 对象销毁时会自动执行 join() ,这可能会导致进程阻塞
- Thread::joinable() 检测子线程是否已退出
- Thread::detach() 使子线程独立运行,不再需要 Thread::join()
- Thread::HARDWARE_CONCURRENCY 硬件层支持的并行线程数量
- Thread::$id 获取子线程的 ID
本文暂时没有评论,来添加一个吧(●'◡'●)