>  기사  >  백엔드 개발  >  PHP의 코루틴에 대한 자세한 소개(코드)

PHP의 코루틴에 대한 자세한 소개(코드)

不言
不言원래의
2018-09-13 16:57:283891검색

이 글에서는 먼저 Yield의 사용법과 Generator의 인터페이스에 초점을 맞춰 Generator의 개념을 소개합니다. 코루틴 부분에서는 코루틴의 원리와 PHP 코루틴 프로그래밍 시 주의해야 할 사항을 간략하게 설명합니다.

PHP는 5.5부터 코루틴 프로그래밍을 구현할 수 있는 생성기를 도입했습니다. 이 기사는 생성기 검토로 시작한 다음 코루틴 프로그래밍으로 전환합니다.

yield 및 생성기

Generator

생성기는 반복기 인터페이스를 구현하는 데이터 유형입니다. 생성기 인스턴스는 new를 통해 얻을 수 없으며 생성기 인스턴스를 얻을 수 있는 정적 메서드가 없습니다. 생성기 인스턴스를 얻는 유일한 방법은 생성기 함수(yield 키워드가 포함된 함수)를 호출하는 것입니다. 생성기 함수를 호출하면 생성기 개체가 직접 반환되고, 생성기가 실행 중일 때 함수 내의 코드가 실행되기 시작합니다.

먼저 코드로 이동하여 Yield와 Generator를 직관적으로 경험해보세요.

# generator1.php
function foo() {
    exit('exit script when generator runs.');
    yield;
}

$gen = foo();
var_dump($gen);
$gen->current();

echo 'unreachable code!';

# 执行结果
object(Generator)#1 (0) {
}
exit script when generator runs.

foo 함수에는 yield 키워드가 포함되어 있으며 Generator 함수로 변환됩니다. foo를 호출하면 함수 본문의 코드가 실행되지 않지만 생성기 인스턴스가 반환됩니다. 생성기가 실행된 후 foo 함수 내의 코드가 실행되고 스크립트가 종료됩니다. foo函数包含yield关键字,变身为生成器函数。调用foo不会执行函数体中的任何代码,而是返回一个生成器实例。生成器运行后,foo函数内的代码执行,脚本结束。

如其名,生成器可以用来生成数据。只是其生成数据的方式与其他函数不一样:生成器通过yield返回数据,而非return; yield返回数据后,生成器函数不会销毁,只是暂停运行,未来可以从暂停处恢复运行;生成器运行一次,(只)返回一个数据,多次运行就返回多个数据;不调用生成器获取数据,生成器内的代码就躺着不动,所谓动次打次,说的就是生成器生成数据的样子。

生成器实现了迭代器接口,获取生成器数据可以用foreach循环或手工current/next/valid。如下代码演示数据生成和遍历:

# generator2.php
function foo() {
  # 返回键值对数据
  yield "key1" => "value1";
  $count = 0;
  while ($count < 5) {
    # 返回值,key自动生成
    yield $count;
    ++ $count;
  }
  # 不返回值,相当于返回null
  yield;
}

# 手动获取生成器数据
$gen = foo();
while ($gen->valid()) {
  fwrite(STDOUT, "key:{$gen->key()}, value:{$gen->current()}\n");
  $gen->next();
}

# foreach 遍历数据
fwrite(STDOUT, "\ndata from foreach\n");
foreach (foo() as $key => $value) {
    fwrite(STDOUT, "key:$key, value:$value\n");
}

yield

yield关键字是生成器的核心,其让普通函数异化(进化)为生成器函数。yield有“让出”的意思,程序执行到yield语句会暂停执行,让出CPU并将控制权返回到调用者,下次执行时从中断点继续执行。控制权返回到调用者时,yield语句可以携带值返回给调用方。generator2.php脚本演示了yield返回值的三种形式:

  1. yield $key => $value: 返回数据的key和value;

  2. yield $value: 返回数据,key由系统分配;

  3. yield: 返回null值,key由系统分配;

yield让函数可以随时暂停、继续执行,并返回数据给调用方。如果继续执行时需要外部数据,这个工作由生成器的send函数提供:出现在yield左边等号的变量会接收send传来的值。看一个常见的send函数使用样例:

function logger(string $filename) {
  $fd = fopen($filename, 'w+');
  while($msg = yield) {
    fwrite($fd, date('Y-m-d H:i:s') . ':' . $msg . PHP_EOL);
  }
  fclose($fd);
}

$logger = logger('log.txt');
$logger->send('program starts!');
// do some thing
$logger->send('program ends!');

send让生成器之间和外部有双向数据通信的能力:yield返回数据;send提供继续运行的支撑数据。由于send让生成器继续执行,这个行为与迭代器的next接口类似,next相当于send(null)

其他

  1. $string = yield $data;的表达式在PHP7前不合法,需要加括号:$string = (yield $data);

  2. PHP5生成器函数不能return值,PHP7后可以return值,并通过生成器的getReturn获取返回的值。

  3. PHP7新增了yield from语法,实现了生成器委托。

  4. 生成器是单向迭代器,开动后不能调用rewind

总结

相对于其他迭代器,生成器具有性能开销小、编码容易的特点。其作用主要体现在三个方面:

  1. 数据生成(生产者),通过yield返回数据;

  2. 数据消费(消费者),消费send传来的数据;

  3. 实现协程。

关于PHP中的生成器及基本用法,建议看看 2gua 大佬的博文:PHP之生成器,生动有趣且易懂。

协程编程

协程(coroutine)是随时可中断、恢复执行的子程序,yield

이름에서 알 수 있듯이 생성기를 사용하여 데이터를 생성할 수 있습니다. 다만 데이터를 생성하는 방식이 다른 함수와 다릅니다. 생성기는 yield가 데이터를 반환한 후 return 대신 yield를 통해 데이터를 반환합니다. , 생성합니다. 생성기 기능은 삭제되지 않지만 일시 중지만 되며 나중에 일시 중지 지점에서 다시 시작할 수 있습니다. 생성기는 한 번 실행하면 하나의 데이터만 반환하고 실행하면 여러 데이터를 반환합니다. 여러 번 생성기는 데이터를 얻기 위해 호출되지 않으며 생성기 함수는 하나의 데이터만 반환합니다. 소위 "매번 이동"이라는 방식은 코드가 그대로 유지된다는 의미입니다. 발전기는 데이터를 생성합니다.

생성기는 반복기 인터페이스를 구현합니다. foreach 루프 또는 수동 current/next/valid를 사용하여 생성기 데이터를 얻을 수 있습니다. 다음 코드는 데이터 생성 및 탐색을 보여줍니다.

rrreee

yield

yield 키워드는 일반 함수를 생성기 함수로 소외(진화)시킬 수 있는 생성기의 핵심입니다. yield는 "포기"를 의미합니다. 프로그램이 yield 문을 실행하면 실행이 일시 중지되고 CPU가 호출자에게 제어권을 반환합니다. 실행되면 계속을 클릭하세요. 제어권이 호출자에게 반환되면 yield 문은 호출자에게 값을 반환할 수 있습니다. generator2.php 스크립트는 세 가지 형태의 수익률 반환 값을 보여줍니다. 🎜
  1. 🎜yield $key => and value;🎜
  2. 🎜yield $value: 데이터를 반환하고 키는 시스템에서 할당됩니다. 🎜
  3. 🎜yield: null 값을 반환하며 키는 시스템에서 할당됩니다. /li>
🎜yield를 사용하면 함수가 일시 중지되고 언제든지 실행을 계속하며 호출자에게 데이터를 반환할 수 있습니다. 실행을 계속하기 위해 외부 데이터가 필요한 경우 이 작업은 생성기의 send 함수에 의해 제공됩니다. yield 왼쪽에 등호와 함께 나타나는 변수는 를 수신합니다. >send 값이 전달되었습니다. send 함수의 일반적인 사용 예를 살펴보세요. 🎜rrreee🎜send는 생성기와 외부 세계 간의 양방향 데이터 통신을 가능하게 합니다. yield code> 반환 데이터; <code>send는 지속적인 작업을 위한 지원 데이터를 제공합니다. send를 사용하면 생성기가 계속 실행될 수 있으므로 이 동작은 반복자의 next 인터페이스와 유사합니다. 여기서 nextsend( null). 🎜🎜Others🎜<ol class=" list-paddingleft-2"> <li>🎜<code>$string = Yield $data; 표현식은 PHP7 이전에는 유효하지 않으며 괄호가 필요합니다: $string = (yield $data);🎜
  • 🎜PHP5 생성기 함수는 값을 반환할 수 없습니다. PHP7 이후에는 값을 반환하고 생성기의 getReturn을 전달할 수 있습니다. 반환된 값을 가져옵니다. 🎜
  • 🎜PHP7은 생성기 위임을 구현하기 위해 yield from 구문을 추가합니다. 🎜
  • 🎜생성기는 단방향 반복기이므로 시작된 후에는 rewind를 호출할 수 없습니다. 🎜
  • 🎜요약🎜🎜다른 반복기에 비해 생성기는 성능 오버헤드가 낮고 코딩이 쉽다는 특징을 가지고 있습니다. 그 역할은 주로 세 가지 측면에 반영됩니다: 🎜
    1. 🎜데이터 생성(생산자), Yield를 통해 데이터 반환 🎜
    2. 🎜데이터 소비(소비자) ), 전송된 데이터를 소비합니다. 🎜
    3. 🎜코루틴을 구현합니다. 🎜
    🎜PHP의 생성기와 기본 사용법에 대해서는 2gua의 블로그 게시물을 읽는 것이 좋습니다: 활기차고 흥미롭고 이해하기 쉬운 PHP 생성기. 🎜🎜코루틴 프로그래밍🎜🎜코루틴은 언제든지 중단하고 다시 시작할 수 있는 서브루틴입니다. yield 키워드를 사용하면 함수에 이러한 기능이 있으므로 코루틴 프로그래밍에 사용할 수 있습니다. 🎜🎜프로세스, 스레드 및 코루틴🎜🎜스레드는 프로세스에 속하며 프로세스는 여러 스레드를 가질 수 있습니다. 프로세스는 컴퓨터의 자원 할당을 위한 최소 단위이고, 스레드는 컴퓨터의 스케줄링 및 실행을 위한 최소 단위입니다. 프로세스와 스레드는 모두 운영 체제에 의해 예약됩니다. 🎜🎜코루틴은 사용자 프로그램이 스케줄링을 구현해야 하는 "사용자 모드 스레드"로 간주될 수 있습니다. 스레드와 프로세스는 "선점형" 방식으로 교대로 실행되도록 운영 체제에 의해 예약되며, 코루틴은 "협상형" 방식으로 교대로 실행되도록 CPU를 적극적으로 포기합니다. 코루틴은 매우 가볍고, 코루틴 전환에는 스레드 전환이 포함되지 않으며, 실행 효율성이 높을수록 코루틴의 장점을 더 잘 반영할 수 있습니다. 🎜

    생성기 및 코루틴

    생성기에 의해 구현된 코루틴은 스택리스 코루틴입니다. 즉, 생성기 함수에는 런타임 구현 중에 호출자의 스택에 첨부되는 함수 프레임만 있습니다. 강력한 스택형 코루틴과 달리 생성기는 프로그램이 일시 중지된 후에 프로그램의 방향을 제어할 수 없으며 호출자에게 제어권을 수동적으로 반환할 수만 있습니다. 생성기는 전체 코루틴이 아닌 자체 중단만 할 수 있습니다. 물론 생성기의 장점은 매우 효율적이고(일시 중지할 때 프로그램 카운터만 저장하면 됨) 구현이 간단하다는 것입니다.

    코루틴 프로그래밍

    PHP의 코루틴 프로그래밍에 관해 말하자면, 대부분의 사람들이 Niao 형제가 재인쇄(번역)한 이 블로그 게시물을 읽었을 것입니다. PHP에서 코루틴 구현 멀티 태스킹 구현 일정. 원저자 nikic은 PHP의 핵심 개발자이자 생성기 기능의 창시자이자 구현자입니다. 제너레이터와 이를 기반으로 한 코루틴 프로그래밍에 대해 자세히 알아보려면 제너레이터에 대한 nikic의 RFC와 Niaoge 웹사이트의 기사를 꼭 읽어야 합니다.

    먼저 생성기 기반 코루틴이 어떻게 작동하는지 살펴보겠습니다. 코루틴은 협력적으로 작동합니다. 즉, 코루틴은 여러 작업의 대체 실행을 달성하기 위해 CPU를 적극적으로 포기합니다(즉, 동시 멀티태스킹이지만 병렬은 아님). ; 생성기는 코루틴으로 간주될 수 있습니다. yield 문이 실행되면 CPU 제어권이 호출자에게 반환되고 호출자는 계속해서 다른 코루틴이나 다른 코드를 실행합니다. yield语句,让出CPU控制权回到调用方,调用方继续执行其他协程或其他代码。

    再来看鸟哥博客理解的难点何在。协程非常轻量,一个系统中可以同时存在成千上万个协程(生成器)。而操作系统不会对协程调度,安排协程执行的工作就落到开发者身上。部分人看不懂鸟哥文章的协程部分,是因为里面说协程编程少(写协程主要就是写生成器函数),而是花笔墨实现了一个协程的调度器(scheduler或者kernel):模拟了操作系统,对所有协程进行公平调度。PHP开发一般的思维是:我写了这些代码,PHP引擎会调用我这些代码得到预期结果。而协程编程不仅要写干活的代码,还要写指导这些代码什么时候干活的代码。没有很好的把握作者的思维,理解起来自然会难一些。需要自行调度,这是生成器协程相对于原生协程(async/await形式)的一个缺点。

    知道了协程是怎么回事,那么它能用来干什么?协程自行让出CPU来协作高效利用CPU,让出的时机当然应该是程序阻塞时。什么地方会让程序阻塞呢?用户态的代码鲜有阻塞,阻塞主要是系统调用。而系统调用的大头是IO,所以协程的主要应用场景在网络编程。为了让程序高性能、高并发,程序应该异步执行不能阻塞。既然异步执行,就需要通知和回调,写回调函数避免不了“回调地狱(callback hell)”的问题:代码可读性差,程序执行流程散落在层层回调函数中等。解决回调地狱的方式主要有两种:Promise和协程。协程能以同步的方式编写代码,在高性能网络编程(IO密集型)中是推荐的。

    再回过头看PHP中的协程编程。PHP中基于生成器实现实现协程编程,优先推荐使用RecoilPHPAmp等协程框架。这些框架已经写好了调度器,在其上开发直接写生成器函数,内核会自动调度执行(想让一个函数以协程方式调度执行,在函数体内加上yield即可)。如果不想用yield方式进行协程编程,推荐swoole或其衍生框架,能做到类似golang的协程编程体验,又能享受PHP的开发效率。

    如果想用原生态的做PHP协程编程,类似鸟哥博客中的调度器必不可少。调度器调度协程执行,协程中断后控制权又回到调度器中。所以调度器应该总是在主(事件)循环中,即CPU不在执行协程,就应当在执行调度器的代码。无协程运行时,调度器应当自我阻塞避免消耗CPU(鸟哥博客中使用了内置的select系统调用),等待事件到来再执行相应的协程。程序运行期间,除了调度器阻塞,协程在运行过程中不应该调用阻塞API。

    总结

    在协程编程中,yield的主要作用是将控制权转让,无需纠结于其返回值(基本上yield返回的值会在下次执行时直接send过来)。重点应当关注控制权转让的时机,以及协程的运作方式。

    另外需要说明一点,协程和异步没有多大关系,还要看运行环境支撑。常规的PHP运行环境,即使用了promise/coroutine,也还是同步阻塞的。再牛逼的协程框架,sleep一下也不好使了。作为类比,即使JavaScript不使用promise/async这些技术,也是异步非阻塞的。

    通过生成器和Promise,能实现类似于await

    브라더버드의 블로그를 이해하는데 어려움이 있는지 살펴보겠습니다. 코루틴은 매우 가볍고 수천 개의 코루틴(생성기)이 시스템에 동시에 존재할 수 있습니다. 운영 체제는 코루틴을 예약하지 않으며 코루틴 실행을 준비하는 작업은 개발자의 몫입니다. 어떤 사람들은 코루틴 프로그래밍이 거의 없다고 Niao 형제의 기사에서 코루틴 부분을 이해하지 못하지만(코루틴 작성은 주로 생성기 함수 작성을 의미함) 코루틴 스케줄러(스케줄러 또는 커널)를 구현하는 데 많은 시간을 소비합니다. 운영 체제를 관리하고 모든 코루틴에 대해 공정한 스케줄링을 수행합니다. PHP 개발에 대한 일반적인 생각은 다음과 같습니다. 내가 이 코드를 작성하면 PHP 엔진이 내 코드를 호출하여 예상되는 결과를 얻습니다. 코루틴 프로그래밍에는 작업을 수행하기 위한 코드 작성뿐만 아니라 이러한 코드에 작업 시기를 지시하는 코드 작성도 필요합니다. 작가의 생각을 제대로 파악하지 못하면 당연히 이해하기 어려울 것이다. 자체적으로 예약해야 하는데 이는 기본 코루틴(async/await 형식)에 비해 생성기 코루틴의 단점입니다.

    이제 코루틴이 무엇인지 알았으니 어떤 용도로 사용할 수 있을까요? 코루틴은 CPU를 협력하고 효율적으로 활용하기 위해 스스로 CPU를 포기합니다. 물론, 포기해야 할 시점은 프로그램이 차단될 때입니다. 프로그램은 어디에서 차단됩니까? 사용자 모드 코드는 거의 차단하지 않으며 차단은 주로 시스템 호출에 의해 발생합니다. 대부분의 시스템 호출은 IO이므로 코루틴의 주요 애플리케이션 시나리오는 네트워크 프로그래밍입니다. 프로그램을 고성능 및 높은 동시성으로 만들기 위해서는 프로그램이 차단되지 않고 비동기적으로 실행되어야 합니다. 비동기 실행에는 알림과 콜백이 필요하기 때문에 콜백 함수를 작성하면 "콜백 지옥" 문제를 피할 수 없습니다. 코드 가독성이 떨어지고 프로그램 실행 프로세스가 콜백 함수 계층에 분산되어 있습니다. 콜백 지옥을 해결하는 두 가지 주요 방법은 Promise와 Coroutine입니다. 코루틴은 동기식으로 코드를 작성할 수 있으며 고성능 네트워크 프로그래밍(IO 집약적)에 권장됩니다.

    PHP의 코루틴 프로그래밍을 다시 살펴보겠습니다. PHP에서는 코루틴 프로그래밍이 생성기를 기반으로 구현됩니다. RecoilPHPAmp와 같은 코루틴 프레임워크를 사용하는 것이 좋습니다. 이러한 프레임워크에는 이미 스케줄러가 작성되어 있습니다. 생성기 함수를 직접 개발하면 커널이 자동으로 실행을 예약합니다. 함수를 코루틴 모드에서 실행하도록 예약하려면 yield를 추가하세요. 함수 본문, 즉 Can). 코루틴 프로그래밍에 yield 메서드를 사용하고 싶지 않다면 swoole 또는 그 파생 프레임워크를 권장합니다. 이는 golang과 유사한 코루틴 프로그래밍 경험을 달성하고 PHP의 개발 효율성.
    원본 PHP 코루틴 프로그래밍을 사용하려면 Niao Ge의 블로그에 있는 것과 유사한 스케줄러가 필수적입니다. 스케줄러는 코루틴 실행을 예약합니다. 코루틴이 중단되면 제어권이 스케줄러로 돌아갑니다. 따라서 스케줄러는 항상 메인(이벤트) 루프에 있어야 합니다. 즉, CPU가 코루틴을 실행하지 않을 때 스케줄러 코드를 실행해야 합니다. 코루틴 없이 실행할 때 스케줄러는 CPU 소비를 피하기 위해 자체적으로 차단해야 하며(Niao Ge의 블로그에서는 내장된 select 시스템 호출을 사용함) 해당 코루틴을 실행하기 전에 이벤트가 도착할 때까지 기다려야 합니다. 프로그램 실행 중에는 스케줄러 차단을 제외하고 코루틴이 실행 중에 차단 API를 호출해서는 안 됩니다.

    #🎜🎜#Summary#🎜🎜##🎜🎜#코루틴 프로그래밍에서 yield의 주요 기능은 반환 값에 대해 걱정하지 않고 제어권을 전달하는 것입니다(기본적으로 yield는 다음 실행 중에 직접 전송됩니다). 제어권이 전달되는 타이밍과 코루틴이 작동하는 방식에 초점을 맞춰야 합니다. #🎜🎜##🎜🎜# 게다가 코루틴은 비동기와 거의 관련이 없으며 운영 환경 지원에 따라 다르다는 점을 설명해야 합니다. 기존 PHP 운영 환경에서는 promise/coroutine을 사용해도 여전히 동기적으로 차단됩니다. 코루틴 프레임워크가 아무리 훌륭하더라도 sleep은 사용하기 쉽지 않습니다. 비유하자면, JavaScript가 약속/비동기 기술을 사용하지 않더라도 비동기적이고 비차단적입니다. #🎜🎜##🎜🎜# 제너레이터와 Promise를 통해 await와 유사한 코루틴 프로그래밍을 구현할 수 있습니다. Github에는 관련 코드가 많이 있으므로 이 글에서는 다루지 않겠습니다. #🎜🎜##🎜🎜#관련 추천: #🎜🎜##🎜🎜##🎜🎜#PHP Medium $_SERVER 상세 소개#🎜🎜##🎜🎜##🎜🎜#

    PHP의 출력 버퍼링에 대한 자세한 소개, 출력 버퍼링_PHP 튜토리얼

    위 내용은 PHP의 코루틴에 대한 자세한 소개(코드)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

    성명:
    본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.

    관련 기사

    더보기