一、关于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。
0、PHP手册说明pthreads
1、github地址
https://github.com/krakjoe/pthreadsgithub中有很多example可以参考。
二、编译安装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
2、使用synchronized实现计数器
pthreads v3下的同步处理synchronized
参考:
https://github.com/krakjoe/pthreads
Wikipedia:Thread (computing)
掘金:多线程编程 – PHP实现
PHP 高级编程之多线程
深入研究PHP及Zend Engine的线程安全模型