编程须知

这个频道内会详细介绍异步编程与同步编程的不同之处以及需要注意的事项。

注意事项

  • 不要在代码中执行sleep以及其他睡眠函数,这样会导致整个进程阻塞
  • exit/die是危险的,会导致worker进程退出
  • 可通过register_shutdown_function来捕获致命错误,在进程异常退出时做一些请求工作
  • PHP代码中如果有异常抛出,必须在回调函数中进行try/catch捕获异常,否则会导致工作进程退出
  • swoole不支持set_exception_handler,必须使用try/catch方式处理异常
  • Worker进程不得共用同一个RedisMySQL等网络服务客户端,Redis/MySQL创建连接的相关代码可以放到onWorkerStart回调函数中。

类/函数重复定义

新手非常容易犯这个错误,由于swoole是常驻内存的,所以加载类/函数定义的文件后不会释放。因此引入类/函数的php文件时必须要使用include_oncerequire_once,否会发生cannot redeclare function/class 的致命错误。

内存管理

PHP守护进程与普通Web程序的变量生命周期、内存管理方式完全不同。请参考 swoole_server内存管理 页面。编写swoole_server或其他常驻进程时需要特别注意。

进程隔离

进程隔离也是很多新手经常遇到的问题。修改了全局变量的值,为什么不生效,原因就是全局变量在不同的进程,内存空间是隔离的,所以无效。所以使用swoole开发Server程序需要了解进程隔离问题。

  • 不同的进程中PHP变量不是共享,即使是全局变量,在A进程内修改了它的值,在B进程内是无效的
  • 如果需要在不同的Worker进程内共享数据,可以用RedisMySQL文件Swoole\TableAPCushmget等工具实现
  • 不同进程的文件句柄是隔离的,所以在A进程创建的Socket连接或打开的文件,在B进程内是无效,即使是将它的fd发送到B进程也是不可用的
 

sleep/usleep的影响

在异步IO的程序中,不得使用sleep/usleep/time_sleep_until/time_nanosleep。(下文中使用sleep泛指所有睡眠函数)

  • sleep函数会使进程陷入睡眠阻塞
  • 直到指定的时间后操作系统才会重新唤醒当前的进程
  • sleep过程中,只有信号可以打断
  • 由于swoole的信号处理是基于signalfd实现的,所以即使发送信号也无法中断sleep

swoole提供的swoole_event_addswoole_timer_tickswoole_timer_afterswoole_process::signal异步swoole_client 在进程sleep后会停止工作。swoole_server也无法再处理新的请求。

实例程序

$serv = new swoole_server("127.0.0.1", 9501);
$serv->on('receive', function ($serv, $fd, $from_id, $data) {
    sleep(100);
    $serv->send($fd, 'Swoole: '.$data);
});
$serv->start();

onReceive事件中执行了sleep函数,server在100秒内无法再收到任何客户端请求。

 

exit/die函数的影响

在swoole程序中禁止使用exit/die,如果PHP代码中有exit/die,当前工作的Worker进程、Task进程、User进程、以及swoole_process进程会立即退出。

建议使用try/catch的方式替换exit/die,实现中断执行跳出PHP函数调用栈。

function swoole_exit($msg)
{
    //php-fpm的环境
    if (ENV=='php')
    {
        exit($msg);
    }
    //swoole的环境
    else
    {
        throw new Swoole\ExitException($msg);
    }
}

异常处理的方式比exit/die更友好,因为异常是可控的,exit/die不可控。在最外层进行try/catch即可捕获异常,仅终止当前的任务。Worker进程可以继续处理新的请求,而exit/die会导致进程直接退出,当前进程保存的所有变量和资源都会被销毁。如果进程内还有其他任务要处理,遇到exit/die也将全部丢弃。

 

while循环的影响

异步程序如果遇到死循环,事件将无法触发。异步IO程序使用Reactor模型,运行过程中必须在reactor->wait处轮询。如果遇到死循环,那么程序的控制权就在while中了,reactor无法得到控制权,无法检测事件,所以IO事件回调函数也将无法触发。

密集运算的代码不是阻塞

实例程序

$serv = new swoole_server("127.0.0.1", 9501);
$serv->on('receive', function ($serv, $fd, $from_id, $data) {
    while(1)
    {
        $i ++;
    }
    $serv->send($fd, 'Swoole: '.$data);
});
$serv->start();

onReceive事件中执行了死循环,server在无法再收到任何客户端请求,必须等待循环结束才能继续处理新的事件。