一、关于pthreads扩展
PHP本身不支持多线程,如果想再CLI模式下实现多线程,需要通过扩展pthreads。pthreads 是一组允许用户在PHP中使用多线程技术的面向对象的API。基于Posix Threads。
注:
POSIX(Portable Operating System Interface)是IEEE为要在各种UNIX操作系统上运行软件,而定义API的一系列互相关联的标准的总称, 也叫可移植操作系统接口。
POSIX线程(POSIX threads),简称Pthreads,是线程的POSIX标准。该标准定义了创建和操纵线程的一整套API。在类Unix操作系统(Unix、Linux、Mac OS X等)中,都使用Pthreads作为操作系统的线程。Windows操作系统也有其移植版pthreads-win32。
二、编译安装Pthreads
0、下载扩展
git clone https://github.com/krakjoe/pthreads.git
1、需要确认PHP是指定线程安全的方式编译的
/usr/local/php/bin/php --info | grep Thread
Thread Safety => enabled
如果不是线程安全的需要带上参数–enable-maintainer-zts,重新编译。
2、编译按照pthreads扩展
cd pthreads/
/usr/local/php/bin/phpize
./configure --with-php-config=/usr/local/php/bin/php-config
make && make install
4、php.ini中添加扩展
把pthreads.so添加到php.ini文件中
/usr/local/php/bin/php -m | grep pthread
pthreads
5、测试
以下代码保存文件pthreads.php
$thread = new class extends Thread {
public function run() {
echo "Hello World\n";
}
};
$thread->start() && $thread->join();
执行以上代码
/usr/local/php/bin/php pthreads.php
Hello World
三、多线程原理
0、TSRM机制
多线程模式下,多个线程共用一个进程的地址空间,即多个线程共享一个全局变量,这个时候就会产生竞争,也就是常说的这个全局变量是非线程安全的。PHP引入了一个TSRM(Thread-Safe Resource Manager)机制来解决线程安全问题。
这个机制的主要思想是:对于多线程模型来说,每当一个新的线程被创建,就单独的分配一块内存,这块内存存储着一个全局变量的副本。而这块内存会被一个Vector串起来,由Zend统一管理。(参加风雪之隅:揭秘TSRM(Introspecting TSRM))
1、线程类
在pthread扩展中,提供了创建多线程应用需要的全套工具。主要包含以下几个类,Threaded、Thread、Worker、Pool、Mutex等。
1.0、Threaded
提供多线程操作的基本功能。主要的是run方法, 每个线程都要实现此方法,线程开始运行后,此方法中的代码会自动执行;
1.1、Thread
继承了Threaded类,实现了run方法。Thread类中常用方法
Thread::start() 开始运行一个新线程,并且执行继承的run方法。
Thread::getThreadId() 获取当前线程ID。
Thread::join() 等待线程结束。
Thread::kill() 强制线程结束。
注:创建自己的线程类,通常直接继承Thread类。
1.2、Mutex(pthread 小于2.0版本,2.0以上使用Threaded::synchronized)
互斥锁相关功能。常用方法
Mutex::create 创建一个互斥锁
Mutex::lock 给互斥量加锁
Mutex::unlock 释放互斥量上的锁
Mutex::destroy 释放互斥锁
四、多线程实践
一、一个进程中创建多个线程执行打印操作
0、代码(以下保存文件multi_thread.php)
class workerThread extends Thread {
private $i = null;
public function __construct($i) {
$this->i = $i;
}
public function run() {
printf("%s is Thread #%lu, Pid=%s\n", __CLASS__, $this->getThreadId(), getmypid());
while(true) {
echo $this->i, PHP_EOL;
sleep(10);
}
}
}
for($i = 0; $i < 10; $i++) {
$workers[$i] = new workerThread($i);
$workers[$i]->start();
}
1、执行结果
[root@izj6cfhaw27k49x8usszs3z thread]# php7 multi_thread.php
workerThread is Thread #140040849848064, Pid=6554
0
workerThread is Thread #140040764126976, Pid=6554
1
workerThread is Thread #140040755734272, Pid=6554
2
workerThread is Thread #140040747341568, Pid=6554
3
workerThread is Thread #140040738948864, Pid=6554
4
workerThread is Thread #140040726378240, Pid=6554
5
workerThread is Thread #140040713795328, Pid=6554
6
workerThread is Thread #140040361473792, Pid=6554
7
workerThread is Thread #140040353081088, Pid=6554
8
workerThread is Thread #140040344688384, Pid=6554
9
并行的打印出了上面的结果,等9后推出。
2、思考
把run方法中while去掉,也是同样的结果,为啥没有进入while轮询呢
二、多线程实现计数器程序
0、代码(以下代码保存在文件pthread_multi.php)
$counter = 0;
$handle = fopen("/tmp/counter.txt", "w");
fwrite($handle, $counter );
fclose($handle);
class CounterThread extends Thread {
public function __construct($mutex = null) {
$this->mutex = $mutex;
$this->handle = fopen("/tmp/counter.txt", "w+");
}
public function run() {
if($this->mutex)
$locked = Mutex::lock($this->mutex);
$counter = intval(fgets($this->handle));
$counter++;
rewind($this->handle);
fputs($this->handle, $counter);
printf("Thread #%lu says: %s\n", $this->getThreadId(), $counter);
if($this->mutex)
Mutex::unlock($this->mutex);
}
public function __destruct(){
fclose($this->handle);
}
}
//没有互斥锁
for ($i = 0; $i < 10; $i++) {
$threads[$i] = new CounterThread();
$threads[$i]->start();
}
// 加入互斥锁
$mutex = Mutex::create(true);
for ($i = 0; $i < 50; $i++) {
$threads[$i] = new CounterThread($mutex);
$threads[$i]->start();
}
Mutex::unlock($mutex);
for ($i = 0; $i < 50; $i++) {
$threads[$i]->join();
}
Mutex::destroy($mutex);
1、执行
使用到了Mutex类,需要在pthread2.0以下版本使用。
php pthread_multi.php
查看pthread版本
/usr/local/php/bin/php --info | grep -C 10 -i 'pthread'
pthreads
Version => 3.2.1dev
执行报错”zend_mm_heap corrupted”(更多solutin点击stackoverflow What does zend_mm_heap corrupted mean)
在命令行执行如下命令
export USE_ZEND_ALLOC=0