前言
之前由于项目需求,采用了PHP 的pthread扩展编写异步并发程序。需求是这样的:单台服务器需要将每秒内上报的战报上传至服务器并保证上报成功率在99.9%以上,由于合作平台极其不稳定,大概率上报超时涉及重发。那么上报就必须使用异步队列,但是进程是很消耗系统资源,PHP本身是不支持线程的,最后采用了PHP 的pthread扩展进行多线程编程。(期间用过swoole所谓协程,测试数据差强人意;想过go重写,开发周期有限)
由于网上关于php多线程编程资料甚少(PHP本身的弱项),加上pthread缺少报错信息,导致调试难度。留下采坑过程,以供借鉴。
环境安装
由于LNMP模式安装的PHP非线程安装版本为php7.1, 为保持保持一致重编译线程版本也保持php7.1。官方提供7.1 pthread版本无法编译成功(Zend语法不兼容),感谢SKDSKL1提供的 https://github.com/sjdskl/pthreads-php7.1.git 提供的修改版本可成功编译安装。
git clone https://github.com/sjdskl/pthreads-php7.1.git
cd pthreads-php7.1
./configure --with-php-config=/usr/local/php/bin/php-config
make && make install
echo "extension=pthreads.so" >> /usr/local/php/etc/php.ini
/usr/local/php/bin/php Benchmark.php
由于LNMP开启线程安全是不值得的,所以同时存在2个版本的php(Thread Safety和No Thread Safety)。
pthread使用注意事项2
-
线程创建后,不能使用父线程的变量,诸如$GLOBALS或global等用法都无法操作父线程的全局变量
-
线程类的属性不能直接进行哈希表(数组)操作,如:
//这样是无效的
$this->var1["hello"] = "world";
//改为
$this->var1 = ["hello"=>"world"];
因为线程类属性的赋值是通过序列化实现的,其本质是存储了序列化数据。
-
不能调用父线程的redis、mysql等非标量的成员变量(引用类型的对象,涉及父线程的内存空间),如果是标量就可以。
-
最佳实践:为了安全起见,建议不要引用父线程的任意变量,用到的参数都通过Thread构造函数传入,可以把php线程当成轻量级的进程,不要做共享内存的操作。
线程类
<?php
/**
* Created by PhpStorm.
* User: liugaoyun
* Date: 2018/8/4
* Time: 上午11:51
*/
namespace console\threads;
class DemoThread extends \Thread
{
public $dbConfig = null; //db配置
public $redisConfig = null; //redis配置
public $debug = false; //是否是debug模式
public function __construct($dbConfig, $redisConfig, $debug = false)
{
$this->dbConfig = (array)$dbConfig;
$this->redisConfig = (array)$redisConfig;
$this->debug = $debug;
}
public function run() {
//TODO:DB初始化
$pdo = new \PDO($this->dbConfig['dsn'], $this->dbConfig["username"], $this->dbConfig["password"],array(\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION));
$pdo->exec("SET NAMES '" . $this->dbConfig["charset"] . "' COLLATE '" . $this->dbConfig["collation"] . "'");
$pdo->exec("SET CHARACTER SET '" . $this->dbConfig["charset"] . "'");
$pdo->setAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE, \PDO::FETCH_OBJ);
//TODO:redis初始化
$redis = new \Redis();
$redis->pconnect($this->redisConfig