hyperf 协程作用和相关的方法

什么是协程

协程是一种轻量级的线程,由用户代码来调度和管理,而不是由操作系统内核来进行调度,也就是在用户态进行

判断当前是否处于协程环境内

在一些情况下我们希望判断一些当前是否运行于协程环境内,

对于一些兼容协程环境与非协程环境的代码来说会作为一个判断的依据,

我们可以通过 Hyperf\Coroutine\Coroutine::inCoroutine(): bool 方法来得到结果。

创建协程的三种方式

Hyperf 是运行于 Swoole 4 的协程和 Swow 协程之上的,这也是 Hyperf 能提供高性能的其中一个很大的因素。

1. 通过co/go来创建协程

只需通过 co(callable callable) 或 go(callable callable) 函数或 Hyperf\Utils\Coroutine::create(callable $callable) 即可创建一个协程,协程内可以使用协程相关的方法和客户端

co

php 复制代码
    public function co()
    {
        co(function () {
            sleep(1);
            echo "hello world 2 \n";
        });
        co(function () {
            sleep(1);
            echo "hello world 3 \n";
        });
    }

go

php 复制代码
 public function go()
    {
        go(function () {
            sleep(2);
            echo "hello world 66 \n";
        });
        go(function () {
            sleep(1);
            echo "hello world 77 \n";
        });
    }

2. Coroutine::create

php 复制代码
    public function coroutine()
    {
        Coroutine::create(function () {
            sleep(1);
            echo "hello world 88 \n";
        });
        Coroutine::create(function () {
            sleep(2);
            echo "hello world 99 \n";
        });
    }
php 复制代码
    public function coroutine()
    {
        $coroutine = new Coroutine();
        $coroutine->create(function () {
            sleep(1);
            echo "hello world 33 \n";
        });
        $coroutine->create(function () {
            sleep(2);
            echo "hello world 44 \n";
        });
    }

3. 待完善

获得当前协程的 ID

php 复制代码
    public function getCoroutineId()
    {
       
       $croutineId =  Coroutine::id();
       return json_encode(['croutineId'=> $croutineId]);
    }

Channel 通道

什么是channel呢,和go的chan一样,就是获取协程之间的数据通信

Channel 与 PHP 的数组类似,仅占用内存,没有其他额外的资源申请,所有操作均为内存操作,无 I/O 消耗,使用方法与 SplQueue 队列类似。

python 复制代码
   public function channal(){
       $chan =  new \Swoole\Coroutine\Channel();
       co(function () use ($chan) {
        $chan->push('I am an alien');
        });
       $data =  $chan->pop();
       return json_encode(['data'=> $data]);
    }

Defer 特性

当我们希望在协程结束时运行一些代码时,可以通过 defer(callable $callable) 函数或 Hyperf\Coroutine::defer(callable $callable) 将一段函数以 栈(stack) 的形式储存起来,栈(stack) 内的函数会在当前协程结束时以 先进后出 的流程逐个执行。

php 复制代码
    public function channal(){
       $chan =  new \Swoole\Coroutine\Channel();
       co(function () use ($chan) {
        $chan->push('I am an alien');
        defer(function(){
            $this->defers();
        });
        });

       $data =  $chan->pop();
       return json_encode(['data'=> $data]);
    }

    public function defers(){
          echo "hello world 111 \n";
    }


WaitGroup 特性

WaitGroup 是基于 Channel 衍生出来的一个特性,如果接触过 Go 语言,我们都会知道 WaitGroup 这一特性,在 Hyperf 里,WaitGroup 的用途是使得主协程一直阻塞等待直到所有相关的子协程都已经完成了任务后再继续运行,这里说到的阻塞等待是仅对于主协程(即当前协程)来说的,并不会阻塞当前进程。

我们通过一段代码来演示该特性:

php 复制代码
    public function waitGroup(){
        $wg = new \Swoole\Coroutine\WaitGroup();
        $wg->add();
        go(function () use ($wg) {
            sleep(10);
            $wg->done();
        });
        go(function () use ($wg) {
            sleep(5);
            $wg->done();
        });
        $wg->wait();
        echo "hello world 55 \n";
    }

结果:

5秒后才执行主协程的内容

php 复制代码
hello world 55

Parallel 特性

Parallel 特性是 Hyperf 基于 WaitGroup 特性抽象出来的一个更便捷的使用方法,我们通过一段代码来演示一下。

php 复制代码
<?php
declare(strict_types=1);
namespace App\Controller;

use Hyperf\Utils\Exception\ParallelExecutionException;
use Hyperf\HttpServer\Annotation\AutoController;
use Hyperf\Utils\Coroutine;
use Hyperf\Utils\Parallel;

/**
 * @AutoController()
 */
class WaitGroupController extends AbstractController
{
           
    public function index()
    {
 
        $parallel = new Parallel();
        $parallel->add(function () {
            sleep(1);
            return Coroutine::id();
        });
        $parallel->add(function () {
            sleep(1);
            return Coroutine::id();
        });
    
        try{
            // $results 
        $results = $parallel->wait(); 
        return json_encode($results);
        } catch(ParallelExecutionException $e){
            // $e->getResults() 获取协程中的返回值。
            // $e->getThrowables() 获取协程中出现的异常。
            echo $e->getMessage() . PHP_EOL;
        }
    
    }   
}

上面的代码可以简化成下面的代码

php 复制代码
<?php
use Hyperf\Utils\Coroutine;

// 传递的数组参数您也可以带上 key 便于区分子协程,返回的结果也会根据 key 返回对应的结果
$result = parallel([
    function () {
        sleep(1);
        return Coroutine::id();
    },
    function () {
        sleep(1);
        return Coroutine::id();
    }
]);

限制 Parallel 最大同时运行的协程数

防止访问过大,导致服务器崩溃,可以设置协程同时运行的最大数量

5就是限制数量的

python 复制代码
$parallel = new Parallel(5);

完整代码

python 复制代码
use Hyperf\Utils\Exception\ParallelExecutionException;
use Hyperf\Utils\Coroutine;
use Hyperf\Utils\Parallel;

$parallel = new Parallel(5);
for ($i = 0; $i < 20; $i++) {
    $parallel->add(function () {
        sleep(1);
        return Coroutine::id();
    });
} 

try{
   $results = $parallel->wait(); 
} catch(ParallelExecutionException $e){
    // $e->getResults() 获取协程中的返回值。
    // $e->getThrowables() 获取协程中出现的异常。
}

Concurrent 协程运行控制

Hyperf\Utils\Coroutine\Concurrent 基于 Swoole\Coroutine\Channel 实现,用来控制一个代码块内同时运行的最大协程数量的特性。

以下样例,当同时执行 10 个子协程时,会在循环中阻塞,但只会阻塞当前协程,直到释放出一个位置后,循环继续执行下一个子协程。

php 复制代码
<?php

<?php
declare(strict_types=1);
namespace App\Controller;

use Hyperf\HttpServer\Annotation\AutoController;
use Hyperf\Utils\Coroutine\Concurrent;


/**
 * 
 * @AutoController()
 */
class WaitGroupController extends AbstractController
{
           
    public function index()
    {
 

        // 传递的数组参数您也可以带上 key 便于区分子协程,返回的结果也会根据 key 返回对应的结果

        $concurrent = new Concurrent(10);
        
        for ($i = 0; $i < 15; ++$i) {
            $concurrent->create(function () use ($i) {
                // Do something...
                print($i."\n");
            });
        }
        return json_encode( ['success'=>'success','count'=> $concurrent->count()]);
    
    }   
}


协程上下文

不能使用传统的php开发,把可变参数放到成员变量里,这样会导致不同协程之间数据混淆

所以出现了 协程上下文里存储可变参数

Hyperf\Utils\Context::set()

通过调用 set(string id, value) 方法储存一个值到当前协程的上下文中,如下:
<?php use Hyperf\Utils\Context; // 将 bar 字符串以 foo 为 key 储存到当前协程上下文中 foo = Context::set('foo', 'bar'); // set 方法会再将 value 作为方法的返回值返回回来,所以 foo 的值为 bar Copy to clipboardErrorCopied ## Hyperf\Utils\Context::get() 通过调用 get(string id, default = null) 方法可从当前协程的上下文中取出一个以 id 为 key 储存的值,如不存在则返回 default ,如下: <?php use Hyperf\Utils\Context; // 从当前协程上下文中取出 key 为 foo 的值,如不存在则返回 bar 字符串 foo = Context::get('foo', 'bar'); Copy to clipboardErrorCopied ## Hyperf\\Utils\\Context::has() 通过调用 \`has(string id) `方法可判断当前协程的上下文中是否存在以 `id \`为 key 储存的值,如存在则返回 true,不存在则返回 false,如下: \`\`\`php \foo = Context::has('foo'); ``` ## Hyperf\Utils\Context::override() 当我们需要做一些复杂的上下文处理,比如先判断一个 key 是否存在,如果存在则取出 value 来再对 value 进行某些修改,然后再将 value 设置回上下文容器中,此时会有比较繁杂的判断条件,可直接通过调用 override 方法来实现这个逻辑,如下: ```php <?php use Psr\Http\Message\ServerRequestInterface; use Hyperf\Utils\Context; // 从协程上下文取出 request 对象并设置 key 为 foo 的 Header,然后再保存到协程上下文中 request = Context::override(ServerRequestInterface::class, function (ServerRequestInterface request) { return request->withAddedHeader('foo', 'bar'); }); ``` ## Swoole Runtime Hook Level 框架在入口函数中提供了 SWOOLE_HOOK_FLAGS 常量,如果您需要修改整个项目的 Runtime Hook 等级,比如想要支持 CURL 协程 并且 Swoole 版本为 v4.5.4 之前的版本,可以修改这里的代码,如下。 ```php <?php ! defined('SWOOLE_HOOK_FLAGS') && define('SWOOLE_HOOK_FLAGS', SWOOLE_HOOK_ALL | SWOOLE_HOOK_CURL); ```

相关推荐
两个人的幸福11 天前
Windows 桌面应用自研 PHP 队列(下):完整代码与六大工程化优化
php
BingoGo13 天前
PHP 泛型之殇 泛型 RFC 提案被拒绝
后端·php
JaguarJack13 天前
PHP 泛型之殇 泛型 RFC 提案被拒绝
后端·php
用户30745969820714 天前
PHP 扩展——从入门到理解
php
鹏仔先生14 天前
拷贝漫画APP下载页PHP程序,后台带免费AI写作
php
云水一下15 天前
从零开始学 PHP 系列(一):PHP 的前世今生与开发环境搭建
开发语言·php
xingpanvip15 天前
星盘接口开发文档:本命盘接口指南
android·开发语言·css·php·lua
酉鬼女又兒15 天前
零基础入门计算机网络运输层:端到端通信核心作用、端口号分类规则、复用分用工作机制及UDP与TCP协议全方位对比详解
网络·网络协议·tcp/ip·计算机网络·考研·udp·php
dog25015 天前
不要再继续优化 TCP
网络协议·tcp/ip·php
Channing Lewis15 天前
PHP 解析 Excel 的那些坑:一次“行号错位”引发的数据丢失
开发语言·php·excel