-
Notifications
You must be signed in to change notification settings - Fork 9
2.编程须知
- 不要在代码中执行
sleep
以及其他睡眠函数,这样会导致整个进程阻塞 -
exit/die
是危险的,会导致Worker
进程退出 - 不支持
set_exception_handler
,必须使用try/catch
方式处理异常 -
Worker
进程不得共用同一个Redis
或MySQL
等网络服务客户端,框架初始化位于onWorkerStart
回调函数中,无须担心该问题。
新手非常容易犯这个错误,由于Swoole
是常驻内存的,所以加载类/函数定义的文件后不会释放。因此引入类/函数的php文件时必须要使用include_once
或require_once
,否则会发生cannot redeclare function/class
的致命错误。
Server
启动后内存管理的底层原理与普通php-cli程序一致。具体请参考Zend VM
内存管理方面的文章。
在事件回调函数返回后,所有局部对象和变量会全部回收,不需要unset
。如果变量是一个资源类型,那么对应的资源也会被PHP底层释放。
function test()
{
$a = new Object;
$b = fopen('/data/t.log', 'r+');
$c = new swoole_client(SWOOLE_SYNC);
$d = new swoole_client(SWOOLE_SYNC);
global $e;
$e['client'] = $d;
}
-
$a
,$b
,$c
都是局部变量,当此函数return
时,这3
个变量会立即释放,对应的内存会立即释放,打开的IO资源文件句柄会立即关闭。 -
$d
也是局部变量,但是return
前将它保存到了全局变量$e
,所以不会释放。当执行unset($e['client'])
时,并且没有任何其他PHP变量
仍然在引用$d
变量,那么$d
就会被释放。
在PHP
中,有3
类全局变量。
- 使用
global
关键词声明的变量 - 使用
static
关键词声明的类静态变量、函数静态变量 -
PHP
的超全局变量,包括$_GET
、$_POST
、$GLOBALS
等
全局变量和对象,类静态变量,保存在Server
对象上的变量不会被释放。需要程序员自行处理这些变量和对象的销毁工作。
class Test
{
static $array = array();
static $string = '';
}
function onReceive($serv, $fd, $reactorId, $data)
{
Test::$array[] = $fd;
Test::$string .= $data;
}
-
在事件回调函数中需要特别注意非局部变量的
array
类型值,某些操作如TestClass::$array[] = "string"
可能会造成内存泄漏,严重时可能发生爆内存,必要时应当注意清理大数组。 -
在事件回调函数中,非局部变量的字符串进行拼接操作是必须小心内存泄漏,如
TestClass::$string .= $data
,可能会有内存泄漏,严重时可能发生爆内存。
- 同步阻塞并且请求响应式无状态的
Server
程序可以设置max_request
和task_max_request
,当Worker
进程/Task
进程结束运行时或达到任务上限后进程自动退出。该进程的所有变量/对象/资源均会被释放回收。 - 程序内在
onClose
或设置定时器
及时使用unset
清理变量,回收资源
Swoole
提供的异步客户端与普通的PHP
变量不同,异步客户端在发起connect
时底层会增加一次引用计数,在连接close
时会减少引用计数。
包括
swoole_client
、swoole_mysql
、swoole_redis
、swoole_http_client
function test()
{
$client = new swoole_client(SWOOLE_TCP | SWOOLE_ASYNC);
$client->on("connect", function($cli) {
$cli->send("hello world\n");
});
$client->on("receive", function($cli, $data){
echo "Received: ".$data."\n";
$cli->close();
});
$client->on("error", function($cli){
echo "Connect failed\n";
});
$client->on("close", function($cli){
echo "Connection close\n";
});
$client->connect('127.0.0.1', 9501);
return;
}
-
$client
是局部变量,常规情况下return时会销毁。 - 但这个
$client
是异步客户端在执行connect
时swoole引擎底层会增加一次引用计数,因此return时并不会销毁。 - 该客户端执行
onReceive
回调函数时进行了close
或者服务器端主动关闭连接触发onClose
,这时底层会减少引用计数,$client
才会被销毁。
进程隔离也是很多新手经常遇到的问题。修改了全局变量的值,为什么不生效,原因就是全局变量在不同的进程,内存空间是隔离的,所以无效。所以使用Swoole
开发Server
程序需要了解进程隔离
问题。
- 不同的进程中PHP变量不是共享,即使是全局变量,在A进程内修改了它的值,在B进程内是无效的
- 如果需要在不同的Worker进程内共享数据,可以用
Redis
、MySQL
、文件
、Swoole\Table
、APCu
、shmget
等工具实现 - 不同进程的文件句柄是隔离的,所以在A进程创建的Socket连接或打开的文件,在B进程内是无效,即使是将它的fd发送到B进程也是不可用的
$server = new Swoole\Http\Server('127.0.0.1', 9500);
$i = 1;
$server->on('Request', function ($request, $response) {
global $i;
$response->end($i++);
});
$server->start();
在多进程的服务器中,$i
变量虽然是全局变量(global
),但由于进程隔离的原因。假设有4
个工作进程,在进程1
中进行$i++
,实际上只有进程1
中的$i
变成2
了,其他另外3
个进程内$i
变量的值还是1
。
正确的做法是使用Swoole
提供的Swoole\Atomic
或Swoole\Table
数据结构来保存数据。如上述代码可以使用Swoole\Atomic
实现。
$server = new Swoole\Http\Server('127.0.0.1', 9500);
$atomic = new Swoole\Atomic(1);
$server->on('Request', function ($request, $response) use ($atomic) {
$response->end($atomic->add(1));
});
$server->start();
-
Swoole\Atomic
数据是建立在共享内存之上的,使用add
方法加1
时,在其他工作进程内也是有效的