首页 > 财经旅游 > 正文

PHP多任务,并发并行,多线程,协程等知识点理解

2019-08-10 10:05:41   来源:东方头条   评论:0

在讲协程之前,先谈谈多进程、多线程、并行和并发。

对于单核处理器,多进程实现多任务的原理是让操作系统给一个任务每次分配一定的 CPU 时间片,然后中断、让下一个任务执行一定的时间片接着再中断并继续执行下一个,如此反复。

由于切换执行任务的速度非常快,给外部用户的感受就是多个任务的执行是同时进行的。

多进程的调度是由操作系统来实现的,进程自身不能控制自己何时被调度,也就是说: 进程的调度是由外层调度器抢占式实现的

而协程要求当前正在运行的任务自动把控制权回传给调度器,这样就可以继续运行其他任务。这与抢占式的多任务正好相反, 抢占多任务的调度器可以强制中断正在运行的任务, 不管它自己有没有意愿。如果仅依靠程序自动交出控制的话,那么一些恶意程序将会很容易占用全部 CPU 时间而不与其他任务共享。

协程的调度是由协程自身主动让出控制权到外层调度器实现的

协程可以理解为纯用户态的线程,通过协作而不是抢占来进行任务切换。

相对于进程或者线程,协程所有的操作都可以在用户态而非操作系统内核态完成,创建和切换的消耗非常低。

简单的说协程 就是提供一种方法来中断当前任务的执行,保存当前的局部变量,下次再过来又可以恢复当前局部变量继续执行。

我们可以把大任务拆分成多个小任务轮流执行,如果有某个小任务在等待系统 IO,就跳过它,执行下一个小任务,这样往复调度,实现了 IO 操作和 CPU 计算的并行执行,总体上就提升了任务的执行效率,这也便是协程的意义

多线程

在单核下,多线程必定是并发的;

不过现在的统一进程的多线程是可以运行在多核CPU下,所以可以是并行的

并发(Concurrency)

是指能处理多个同时性活动的能力,并发事件之间不一定要同一时刻发生。

并行(Parallesim)

是指同时发生的两个并发事件,具有并发的含义,而并发则不一定并行。

多个操作可以在重叠的时间段内进行。

并行和并发区别

并发指的是程序的结构,并行指的是程序运行时的状态

并行一定是并发的,并行是并发设计的一种

单线程永远无法达到并行状态

协程

协程的支持是在生成器的基础上, 增加了可以回送数据给生成器的功能(调用者发送数据给被调用的生成器函数).

这就把生成器到调用者的单向通信转变为两者之间的双向通信.

我们在上篇文章已经讲过了send方法, 下面让我们理解下协程

在没有涉及到异步执行代码之前,我们的代码都是这样的function printNum($max, $caller)

{

for ($i=0; $i<$max; $i++ ) {

echo "调度者:" . $caller . " 打印:" . $i . PHP_EOL;

}

}

printNum(3, "caller1");

printNum(3, "caller2");

# output

调度者:caller1 打印:0

调度者:caller1 打印:1

调度者:caller1 打印:2

调度者:caller2 打印:0

调度者:caller2 打印:1

调度者:caller2 打印:2

使用协程后改进的代码,初稿,手动调整生成器执行# 本代码手动调整了进程执行代码的顺序,当然本代码实现不用协程也可以,只是利用本流程说明协程作用

# 生成器给了我们函数中断,协程[生成器send]给了我们重新唤起生成器函数的能力

function printNumWithGen($max)

{

for ($i=0; $i<$max; $i++ ) {

$res = yield $i;

echo $res;

}

}

$gen1 = printNumWithGen(3);

$gen2 = printNumWithGen(3);

// 手动执行caller1 再 caller2

$gen1->send("调度者: caller1 打印:" . $gen1->current() . PHP_EOL);

$gen2->send("调度者: caller2 打印:" . $gen2->current() . PHP_EOL);

// 手动执行caller1 再 caller2

$gen1->send("调度者: caller1 打印:" . $gen1->current() . PHP_EOL);

$gen2->send("调度者: caller2 打印:" . $gen2->current() . PHP_EOL);

// 手动执行caller2 再 caller1

$gen2->send("调度者: caller2 打印:" . $gen2->current() . PHP_EOL);

$gen1->send("调度者: caller1 打印:" . $gen1->current() . PHP_EOL);

# output

调度者: caller1 打印:0

调度者: caller2 打印:0

调度者: caller1 打印:1

调度者: caller2 打印:1

调度者: caller2 打印:2

调度者: caller1 打印:2

自定义简单定时执行任务示例:class timer {

private $start = 0; // 定时开始时间

private $timer; // 间隔的时间差,单位秒

private $value = 0; // 产生的结果值

private $callback; // 异步回调

private $isEnd = false; // 当前定时器任务是否结束

public function __construct($timer,callable $callback)

{

$this->start = time();

$this->timer = $timer;

$this->callback = $callback;

}

public function run() {

if($this->valid()) {

$callback = $this->callback;

$callback($this->value ++,$this);

$this->start = time();

}

}

/**

* 定时执行检查

*/

public function valid() {

$end = time();

if($end - $this->start >= $this->timer) {

return true;

} else {

return false;

}

}

public function setEnd($isEnd) {

$this->isEnd = $isEnd;

}

public function getEnd() {

return $this->isEnd;

}

}

/**

* 模拟阻塞的协程1

*

*/

function taskObject1() {

$timer = new timer(1,function($value,timer $timer) {

if($value >= 5) {

$timer->setEnd(true);

}

echo "<br>"."A ".$value;

});

$tid = (yield getTaskId());

while (true) {

if($timer->getEnd() == true) {

break;

}

yield $timer->run();

}

}

/**

* 模拟阻塞的协程2

*

*/

function taskObject2() {

$timer = new timer(2,function($value,timer $timer) {

if($value >= 3) {

$timer->setEnd(true);

}

echo "<br>"."B ".$value;

});

$tid = (yield getTaskId());

while (true) {

if($timer->getEnd() == true) {

break;

}

yield $timer->run();

}

}

$scheduler = new Scheduler;

$scheduler->newTask(taskObject1());

$scheduler->newTask(taskObject2());

$scheduler->run();

以上实现的是:

产生两个任务,并行执行,并且给每个任务在执行的时候模拟几秒钟的阻塞;

让协程切换的时候能顺利切换,其中的任务阻塞不相互影响;

思考:

我为什么要做以上这件事情呢?因为我发现协程实现虽然很强大也很有意思,能让多任务并行,但是我在其中一个任务里调用系统函数 sleep() 的时候,阻塞任务会阻止协程切换,其实从协程的实现原理上来书也是这么回事。

那么,我也就想模拟协程阻塞,但是不产生阻塞看是否可行。PHP本身只提供了生成器为协程调用提供了支撑,如果不依赖扩展,没有提供多线程的程序实现方式,没有java那么强大,可以开子线程进行实现。

我印象中java的子线程是独立执行且不会相互阻塞的,所以我在想,PHP既然可以实现类似于多线程这样的机制,那么能不能实现调用过程中非阻塞呢?

经过这样一个实现和思考,一开始是陷入了一个误区的,是由于PHP原生函数 sleep() 阻塞造成的思维误区,那就是认为要想真正实现非阻塞或者说实现异步的话,是必须依赖于语言底层的。

后来,我想明白了一个道理,既然某个方法或者函数在执行过程中,会产生阻塞,那么把当前这个方法换成自定义的,做成非阻塞(相对于整个协程调度来说)不就行了吗?比如上面的定时执行我自己实现了一个。

而另一方面,协程调度本身的目的也是为了把任务执行过程切成尽量小片,从而快速切换执行,达到并行的目的。从这方面来看,协程应该也算是一种程序设计思想。

以下是一个程序切成尽量小片执行的例子:// 一个简单的例子

<?php

function xrange($start, $end, $step = 1) {

for ($i = $start; $i <= $end; $i += $step) {

yield $i;

}

}

foreach (xrange(1, 1000000) as $num) {

echo $num, "\n";

}

这个例子是把原本用 range 生成一个很大的整型数组的方式切换为分片执行,也就是说在遍历的时候再去取到指定的值,从代码上来看,内存消耗相对于之前来说就非常小了。

  责任编辑:

今日推荐

习近平在第五届中国国际进口博览会开幕式上发表致辞

11月4日晚,国家主席习近平以视频方式出席在上海举行的第五届中国国际进口博览会开幕式并发表题为《共创开放繁荣的美好未来》的致辞。新华社记者 李学仁 摄[详细]

中国共产党第二十次全国代表大会闭幕会在京举行

10月22日,中国共产党第二十次全国代表大会闭幕会在北京人民大会堂举行。新华社记者 陈建力 摄[详细]

天天学习|中南海月刊(2022.09)

天天学习金色九月,喜迎丰收。9月,一个个瞬间令人难忘:主持中央深改委会议决定健全关键核心技术攻关新型举国体制;出席中央军委晋升上将军衔仪式颁发命令状;新冠肺炎疫情发生以来首次出访...[详细]