php-多线程概念,扩展安装,代码实现

线程是操作系统中进行运算调度的最小单位。它被包含在进程中,是进程中的实际运作单位。

所谓多线程就是在在一个进程中可以并发多个线程,每条线程并行执行不同的任务。多线程大
大提高了程序的执行效率,一个多线程比单线程被操作系统调度的概率更大。而且更高效。多
个线程可以在多核CPU的多个核心同时运行,加快了运行效率。而且线程间的通信更加简单。

对比多进程程序,多线程有以下特点:
线程的创建和切换的系统开销都比进程要小,所以一定程度上会比多进程更高效;
线程天生的共享内存空间,线程间的通信更简单,避免了进程IPC引入新的复杂度。

在默认情况下PHP是不支持多线程的,要使用多线程需要安装 扩展,目前常用的扩展有pcnlt,
POSIX ,pthreads,但是用的最多的还是pthreads扩展,它通过使用参数来指定来编译PHP时
的线程安全方式,使其支持多线程

在使用线程之前首先要考虑到线程的安全问题,线程安全指的是某个函数或函数库在多线程环境
中被调用时,能够正确的处理多线程之间的共享变量,使得程序的功能能够正确完成。

php多线程使用场景:
1、大量重复性的工作,运算等。
2、批量执行有阻塞的业务,比如,curl请求
3、批量查询数据库,提高响应速度。

参考:https://cloud.tencent.com/developer/article/1050582
https://blog.csdn.net/gavin_new/article/details/65444190
https://www.cnblogs.com/jkko123/p/6351604.html
https://www.cnblogs.com/jkko123/p/6294596.html

  1. 多线程环境安装
    php7.2.18
    重新编译php,参考另一篇笔记

    ./configure 参数加上下面的配置
    –enable-maintainer-zts

    编译必须启用zts(线程安全)支持否则无法安装 pthreads 扩展
    php多线程需要运行在线程安全的php版本。

2、安装 pthreads 扩展
cd /usr/local/src

查看扩展版本:http://pecl.php.net/package/pthreads
php版本为7.2,但是,从pecl下载的pthreads,在make && make install 时会报错,无论是编译安装还是pecl install pthreads-3.1.6

编译安装:失败
	tar -zxvf pthreads-3.1.6.tgz
	cd pthreads-3.1.6
	phpize
	./configure --with-php-config=/usr/local/php/bin/php-config
	make && make install

包安装:失败
	pecl install pthreads-3.1.6

从github下载扩展:成功
	yum install -y git
	git clone https://github.com/krakjoe/pthreads.git
	cd pthreads
	phpize
	./configure --with-php-config=/usr/local/php/bin/php-config
	make && make install

php.ini加上:extension=pthreads.so

查看pthreads是否已经安装
php -m | grep pthreads

特别提示:
	支持 PHP 7 的 pthreads v3 只支持通过 cli 命令行来调用,不支持其他的 sapi,所以
	执行/usr/local//php/sbin/php-fpm 出错。
	解决:
	CLI模式下,php会优先读取php-cli.ini,如果没找到会使用php.ini
	【1】cp php.ini php-cli.ini 添加扩展pthreads
	extension=pthreads.so
	【2】编辑原来的php.ini文件注释掉pthreads扩展
	;extension=pthreads.so
	这样CLI模式下php-cli.ini生效,而php-fpm不会读php-cli.ini

3、线程安全
由于线程共享了进程的内存,所以不可避免多个线程同时操作一个变量的情况,可能出现数据混乱和错误。
PHP 实现的线程安全主要是使用 TSRM 机制对 全局变量和静态变量进行了隔离,将全局变量和静态变量给每个线程都复制了一份,各线程使用的都是主线程的一个备份,从而避免了变量冲突,也就不会出现线程安全问题。

线程的个数尽量和CPU的核数相当,线程数太多了,CPU将会消耗很大资源在资源分配与排队上!

PHP 对多线程的封装保证了线程安全,程序员不用考虑对全局变量加各种锁来避免读写冲突了,同时也减少了出错的机会,写出的代码更加安全。

但由此导致的是,子线程一旦开始运行,主线程便无法再对子线程运行细节进行调整了,线程一定程度上失去了线程之间通过全局变量进行消息传递的能力。

也就是说,php内核为了保证彻底的线程安全,阉割了一部分线程该有的功能;并且,为了这个彻底的安全,增大了变量的额外消耗。

所以,在没有多线程需求的服务器上,尽量编译非线程安全的php

特别说明:
	由于,pthreads扩展,只在cli模式下运行,不支持sapi,所以,LNMP模式中无法使用多线程,这个和多进程一样。
	但是,我们可以使用 Easyswoole, swoole 这样的原生php来搭建的http服务中使用多线程和多进程。

4、类和方法
PHP 将线程 封装成了 Thread 类,线程的创建通过实例化一个线程对象来实现,由于类的封装性,变量的使用只能通过构造函数传入,而线程运算结果也需要通过类变量传出。

常用的 Thread 类方法:
	run():此方法是一个抽象方法,每个线程都要实现此方法,线程开始运行后,此方法中的代码会自动执行;
	start():在主线程内调用此方法以开始运行一个线程;
	join():各个线程相对于主线程都是异步执行,调用此方法会等待线程执行结束;否则将不会等待,主线程继续往下执行,但是主线程是要等待子线程全部结束才会结束的!
	kill():强制线程结束;
	isRunning():返回线程的运行状态,线程正在执行run()方法的代码时会返回 true;
	isStarted()
	isJoined()
	getThreadId():线程id,主线程中使用
	getCurrentThreadId():线程id,子线程中使用
	getCreatorId():主线程id,子线程中使用

实现1:
	class Request extends Thread {
	    public $url;
	    public $response;
	    public function __construct($url) {
	        $this->url = $url;
	    }
	    public function run() {
	        $this->response = file_get_contents($this->url);
	    }
	}

	主线程中:

	$chG = new Request("www.google.com");
	$chB = new Request("www.baidu.com");
	$chG->start();
	$chB->start();
	$chG->join();
	$chB->join();

	$gl = $chG->response;
	$bd = $chB->response;

实现2:
	多线程性能的探索
	test.php ----------------------------------------------------
	<?php
		class Test extends Thread{
			public $url;
			public $response;

			public function __construct($url){
				$this->url = $url;
			}

			public function run(){
				file_get_contents($this->url);
				$this->response = $this->getCurrentThreadId();
			}
		}

		$start = microtime(true);

		$url = 'http://www.baidu.com';
		$p = [];

		for($i = 0; $i < 100; $i++){
			$p[$i] = new Test($url);
			$p[$i]->start();
		}

		for($i = 0; $i < 100; $i++){
			$p[$i]->join();
			// echo $p[$i]->response . PHP_EOL;
		}

		$end = microtime(true);

		echo $end - $start;
		echo PHP_EOL;

	test1.php -------------------------------------------------------
	<?php

		$start = microtime(true);

		$url = 'http://www.baidu.com';

		for ($i = 0; $i < 100; $i++) { 
			file_get_contents($url);
		}

		$end = microtime(true);

		echo $end - $start;
		echo PHP_EOL;


	php test.php
		0.31586194038391
	php test1.php
		2.565948009491

	性能提升了 8 倍!

实现3:
	传入数组到线程中
	参见另一篇笔记。



Worker 与 Stackable 类 
	0、pthreads v.2.0.0 以前使用Stackable;之后使用Threaded。
	1、Worker类的父类是Thread类,因此基本用法和Thread一样。而Worker类相对于Thread类来说,增加了线程复用的功能(以降低创建销毁线程所耗费的资源),通常与Stackable类连用,也就是说worker类既可以当做线
	程使用,也可以当做任务的容器来使用。

	2、Worker 是一个具有持久化上下文的线程对象,通常用来在多个线程中使用。
	当一个 Worker 对象开始之后,会执行它的 run 方法,但是即使 run 方法执行完毕,线程本身也不会消亡,除非遇到以下情况:
		1、Worker 对象超出作用范围(没有指向它的引用了)
		2、代码调用了 Worker 对象的 shutdown 方法
		3、整个脚本终止了

	3、这意味着程序员可以在程序执行过程中重用这个线程上下文: 在 Worker 对象的栈中添加对象(stack方法)会激活 Worker 对象执行被加入对象的 run 方法,例如:
		<?php
			class Task extends Threaded{
				function __construct($no){
					$this->no = $no;
				}
				function run(){
					if($this->no == 2){
						sleep(2);
					}
					echo "task{$this->no}:run".PHP_EOL;

					$this->worker->worker_fun();
				}
			}

			class MyWork extends Worker{
				function __construct(){

				}
				function run(){

				}

				function worker_fun(){

				}
			}

			$t1= new Task(1);
			$t2= new Task(2);
			$t3= new Task(3);
			$my = new MyWork();

			$my->start();

			$my->stack($t1);
			$my->stack($t2);
			$my->stack($t3);

			输出:
			task1:run
			task2:run
			task3:run

	4、总结
		1、虽然是在start后面才加入的任务,但是依然可以被执行,因为worker线程是常驻内存的。
		2、由输出可见,任务是按入栈的先后顺序来执行的,存在阻塞的情况。
		3、worker类可以理解为封装了一个线程代理(容器)来运行线程的业务,是单个线程,而具体的业务则是Threaded,实现了解耦;这种封装的好处是:
			1、简化了多线程的操作,提供了统一的方式创建多线程,比如:我们可以创建10个worker对象并且start,这样就创建了10个常驻内存的空线程,当我们需要对
			业务A使用多线程处理时,可以创建Threaded对象来实现业务,然后将Threaded对象投递(stack)到若干个线程中去处理从而实现多线程处理;并且处理完不用shutdown
			,此时程序运行到业务B,也需要使用多线程,于是重复操作即可。
			2、如果要处理的业务A有些复杂,还可以拆分成多个小的逻辑来依次投递,如上面的例子。
			3、线程得以复用,避免了不断创建和销毁线程带来的开销,并且,程序执行完会自动销毁,无需操作。
			4、这样的容器模式对于框架的封装有很大的用处,而且,强化了对线程的管理。
			5、在Treaded对象中可以使用$this->worker获得其所在的worker对象,从而访问worker对象的public方法和属性,因此可以将一些东西放在worker中,比如:连接mysql,在worker
			的run方法中实例化数据库连接,并且赋值给一个public static $dbh(静态属性)。
			6、通过Treaded对象的属性来存储返回结果。




互斥锁
	Warning:pthreads v3 中已经将 Mutex 类移除。
	因此互斥锁是通过创建文件锁的方法
	class FileLock
	{
		private static $file = "flock.xhxx";

		/**
		* 创建一个锁
		*/
		static function lock_open(){
			return fopen(self::$file,"w+");
		}

		/**
		* 销毁一个锁
		*/
		static function lock_close($f){
			fclose($f);
			unlink(self::$file);
		}

		/**
		* 进入锁定
		*/
		static function lock_lock($f){
			flock($f,LOCK_EX);
		}

		/**
		* 退出锁定
		*/
		static function lock_unlock($f){
			flock($f,LOCK_UN);
		}
	}
	使用:
	require_once('FileLock.php');

	class CounterThread extends Thread {
		public $handler;
		public $mutex;
		public $threadId;
		public function __construct($mutex, $id){
			$this->mutex = $mutex;
			$this->threadId = $id;
			$this->handler = fopen('test.log', 'r');
		}

		public function __destruct()
		{
			fclose($this->handler);
		}

		public function run() {
			FileLock::lock_lock($this->mutex);

			$data = fgets($this->handler);

			echo $this->threadId . '-' . $data . PHP_EOL;

			FileLock::lock_unlock($this->mutex);
		}
	}


	//加入互斥锁
	$mutex = FileLock::lock_open();
	for ($i=0;$i<3;$i++){
		$threads[$i] = new CounterThread($mutex, $i);
		$threads[$i]->start();

	}

	FileLock::lock_unlock($this->mutex);

	for ($i=0;$i<3;$i++){
		$threads[$i]->join();
	}

	FileLock::lock_close($mutex);


线程同步
	有些场景我们不希望  thread->start() 就开始运行程序,而是希望线程等待我们的命令。
	$thread->wait();测作用是 thread->start()后线程并不会立即运行,只有收到 $thread->notify(); 发出的信号后才运行
	

线程池
多线程文件安全读写
多线程与数据连接
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值