csworkman

个人博客

swoole-协程

2021-4-13 Mr Chang swoole

Swoole 实现协程基本概念和底层原理

协程的概念:
    协程可以理解为纯用户态的线程,其通过协作而不是抢占来进行切换,相对于进程或者线程,协程所有的操作都可以在用户态完成,创建和切换的消耗更低,swoole可以根据每一个进程创建一个对应的协程,根据IO的状态合理的调度协程。

在swoole 4.x中  协程取代来异步回调,成为来swoole推荐的编程方式,swoole协好吃呢个解决了异步回调困难的问题,使用协程他是以传统同步编程的方法编写代码,底层自动切换为异步IO,既保证了编程的简单性,又借助异步IO 提升系统的并发能力。

实现原理:
    我们先看一段代码:
        
$server = new Swoole\Http\Server('127.0.0.1', 9501, SWOOLE_BASE);

#1
$server->on('Request', function($request, $response) {
    $mysql = new Swoole\Coroutine\MySQL();
    #2
    $res = $mysql->connect([
        'host' => '127.0.0.1',
        'user' => 'root',
        'password' => 'root',
        'database' => 'test',
    ]);
    #3
    if ($res == false) {
        $response->end("MySQL connect fail!");
        return;
    }
    $ret = $mysql->query('show tables', 2);
    $response->end("swoole response is ok, result=".var_export($ret, true));
});

$server->start();
在这段代码中  我们启动了一个基于swoole 实现的http服务器监听客户端请求,如果有onRequest事件发生,则通过基于swoole协程实现的异步mysql 客户端组件对mysql服务器发起联建请求,并执行查询操作,然后将结果以响应方式返回给http客户端

1.调用 Swoole\Http\Server 的 onRequest 事件回调函数时,底层会调用 C 函数 coro_create 创建一个协程(#1位置),同时保存这个时间点的 CPU 寄存器状态和 ZendVM 堆栈信息;
2.调用 mysql->connect 时会发生 IO 操作,底层会调用 C 函数 coro_save 保存当前协程的状态,包括 ZendVM 上下文以及协程描述信息,并调用 coro_yield 让出程序控制权,当前的请求会挂起(#2位置);
3.协程让出程序控制权后,会继续进入http服务器的事件循环处理其他事件,这时swoole可以继续去初六其他客户端发来的请求,
4.当数据库IO事件完成后,mysql连接成功或失败,底层调用C函数 core_resume 恢复对应的协程,恢复zendVM上下文,继续向下执行PHP代码 #3位置);
5.mysql->query 的执行过程与mysql->connect 一样 ,也会出发IO事件并运行一次协程切换调度;
6.所有操作完成后,调用end方法返回结果,并销毁此协程。

协程的适用场景:
1 高并发服务,入秒杀系统,高性能API接口 RPC服务器  适用协程模式 服务的容错率会大大增加 某些接口出现故障时 不会导致整个系统崩溃
2 爬虫 可实现非常强大的并发能力,即使时非常慢速的网络环境,也可以高效的利用宽带
3 即时通信服务,如IM聊天,游戏服务器,物联网,消息服务器等等,可以确保消息通信完全无阻塞,每个消息包均可即时的被处理

协程引入的问题:

1.协程需要为每个并发保存栈内存 并维护对应的虚拟机状态,如果程序并发很大可能会占用大量内存。

2.协程调度会增加额外的一些cpu 开销

尽管如此,在处理高并发应用时,使用协程带来的优势还是远远高于 PHP 默认的同步阻塞机制

协程vs 线程

1  
Swoole 的协程在底层实现上是单线程的,因此同一时间只有一个协程在工作,协程的执行是串行的,这与线程不同,多个线程会被操作系统调度到多个 CPU 并行执行。


使用时的注意事项

编程范式

  • 协程之间通讯不要使用全局变量或者引用外部变量到当前作用域,而要使用 Channel(后面会介绍具体使用)
  • 项目中如果有扩展 hook 了 zend_execute_ex 或者 zend_execute_internal 这两个函数,需要特别注意一下 C 栈,可以使用 co::set 重新设置 C 栈大小





标签: https://zhuanlan.zhihu.com/p/96471009

评论(0) 浏览(1107)