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


在讲协程之前,先谈谈多进程、多线程、并 行和并发。   对于单核处理器,多进程实现多任务的原理是让操作系统给一个任务每次分配一定的CPU 时间片, 然后中断、让下一个任务执行一定的时间片接着再中断并继续执行下一个,如此反复。 由于切换执行任务的速度非常快,给外部用 户的感受就是多个任务的执行是同时进的。     多进程的调度是由操作系统来实现的,进程自身不能控制自己何时被调度,也就是说: 进程的调度是由外层调度器抢占式实现的而协程要求当前正在运行的任务自动把控制 权回传给调度器,这样就可以继续运行其他任务。这与抢占式的多任务正好相反, 抢占 多任务的调度器可以强制中断正在运行的任务, 不管它自己有没有意愿。如果仅依靠程 序自动交出控制的话,那么一些恶意程序将会很容易占用全部 CPU 时间而不与其他任务共享。     协程的调度是由协程自身主动让出控制权到外层调度器实现的协程可以理解为纯用户态的线程,通过协作 而不是抢占来进行任务切换。相对于进程或者线程,协程所有的操作都可以在用户态而非操作系统内核态完成,创建 和切换的消耗非常低。简单的说协程 就是提供一种方法来中断当前任务的执行,保存当前的局部变量,下次 再过来又可以恢复当前局部变量继续执行。我们可以把大任务拆分成多个小任务轮流执 行,如果有某个小任务在等待系统 IO,就跳过它,执行下一个小任务,这样往复调度, 实现了 IO 操作和 CPU 计算的并行执行,总体上就提升了任务的执行效率,这也便是协程的意义   多线程   在单核下,多线程必定是并发的;不过现在的统一进程的多线程是可以运行在多核 CPU 下,所以可以是并行的并发(Concurrency) 是指能处理多个同时性活动的能力,并发事件之间不一定要同一时刻发生。   并行(Parallesim)   是指同时发生的两个并发事件,具有并发的含义,而并发则不一定并行。多个操作可以在重叠的时间段内进行。 并行和并发区别并发指的是程序的结构,并行指的是程序运 行时的状态并行一定是并发的,并行是并发设计的一种单线程永远无法达到并行状态   协程   协程的支持是在生成器的基础上, 增加了可 以回送数据给生成器的功能(调用者发送数据给被调用的生成器函数). 这就把生成器到调用者的单向通信转变为两者之间的双向通信. 在没有涉及到异步执行代码之前,我们的代码都是这样的
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 '
'.'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 '
'.'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();

以上实现的是:

产生两个任务,并行执行,并且给每个任务 在执行的时候模拟几秒钟的阻塞;让协程切换的时候能顺利切换,其中的任务 阻塞不相互影响;    
PHP