이 글에서는 먼저 Yield의 사용법과 Generator의 인터페이스에 초점을 맞춰 Generator의 개념을 소개합니다. 코루틴 부분에서는 코루틴의 원리와 PHP 코루틴 프로그래밍 시 주의해야 할 사항을 간략하게 설명합니다.
PHP는 5.5부터 코루틴 프로그래밍을 구현할 수 있는 생성기를 도입했습니다. 이 기사는 생성기 검토로 시작한 다음 코루틴 프로그래밍으로 전환합니다.
생성기는 반복기 인터페이스를 구현하는 데이터 유형입니다. 생성기 인스턴스는 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
语句会暂停执行,让出CPU并将控制权返回到调用者,下次执行时从中断点继续执行。控制权返回到调用者时,yield
语句可以携带值返回给调用方。generator2.php
脚本演示了yield返回值的三种形式:
yield $key => $value: 返回数据的key和value;
yield $value: 返回数据,key由系统分配;
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)
。
$string = yield $data;
的表达式在PHP7前不合法,需要加括号:$string = (yield $data)
;
PHP5生成器函数不能return
值,PHP7后可以return值,并通过生成器的getReturn
获取返回的值。
PHP7新增了yield from
语法,实现了生成器委托。
生成器是单向迭代器,开动后不能调用rewind
。
相对于其他迭代器,生成器具有性能开销小、编码容易的特点。其作用主要体现在三个方面:
数据生成(生产者),通过yield返回数据;
数据消费(消费者),消费send传来的数据;
实现协程。
关于PHP中的生成器及基本用法,建议看看 2gua 大佬的博文:PHP之生成器,生动有趣且易懂。
协程(coroutine)是随时可中断、恢复执行的子程序,yield
yield
가 데이터를 반환한 후 return
대신 yield
를 통해 데이터를 반환합니다. , 생성합니다. 생성기 기능은 삭제되지 않지만 일시 중지만 되며 나중에 일시 중지 지점에서 다시 시작할 수 있습니다. 생성기는 한 번 실행하면 하나의 데이터만 반환하고 실행하면 여러 데이터를 반환합니다. 여러 번 생성기는 데이터를 얻기 위해 호출되지 않으며 생성기 함수는 하나의 데이터만 반환합니다. 소위 "매번 이동"이라는 방식은 코드가 그대로 유지된다는 의미입니다. 발전기는 데이터를 생성합니다. 생성기는 반복기 인터페이스를 구현합니다. foreach
루프 또는 수동 current/next/valid
를 사용하여 생성기 데이터를 얻을 수 있습니다. 다음 코드는 데이터 생성 및 탐색을 보여줍니다. rrreee
yieldyield
키워드는 일반 함수를 생성기 함수로 소외(진화)시킬 수 있는 생성기의 핵심입니다. yield
는 "포기"를 의미합니다. 프로그램이 yield
문을 실행하면 실행이 일시 중지되고 CPU가 호출자에게 제어권을 반환합니다. 실행되면 계속을 클릭하세요. 제어권이 호출자에게 반환되면 yield
문은 호출자에게 값을 반환할 수 있습니다. generator2.php
스크립트는 세 가지 형태의 수익률 반환 값을 보여줍니다. 🎜yield
를 사용하면 함수가 일시 중지되고 언제든지 실행을 계속하며 호출자에게 데이터를 반환할 수 있습니다. 실행을 계속하기 위해 외부 데이터가 필요한 경우 이 작업은 생성기의 send
함수에 의해 제공됩니다. yield
왼쪽에 등호와 함께 나타나는 변수는 를 수신합니다. >send
값이 전달되었습니다. send
함수의 일반적인 사용 예를 살펴보세요. 🎜rrreee🎜send
는 생성기와 외부 세계 간의 양방향 데이터 통신을 가능하게 합니다. yield code> 반환 데이터; <code>send
는 지속적인 작업을 위한 지원 데이터를 제공합니다. send
를 사용하면 생성기가 계속 실행될 수 있으므로 이 동작은 반복자의 next
인터페이스와 유사합니다. 여기서 next
는 send( null). 🎜🎜Others🎜<ol class=" list-paddingleft-2">
<li>🎜<code>$string = Yield $data;
표현식은 PHP7 이전에는 유효하지 않으며 괄호가 필요합니다: $string = (yield $data)
;🎜
반환
할 수 없습니다. PHP7 이후에는 값을 반환하고 생성기의 getReturn을 전달할 수 있습니다.
반환된 값을 가져옵니다. 🎜yield from
구문을 추가합니다. 🎜rewind
를 호출할 수 없습니다. 🎜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中基于生成器实现实现协程编程,优先推荐使用RecoilPHP
、Amp
等协程框架。这些框架已经写好了调度器,在其上开发直接写生成器函数,内核会自动调度执行(想让一个函数以协程方式调度执行,在函数体内加上yield
即可)。如果不想用yield
方式进行协程编程,推荐swoole
或其衍生框架,能做到类似golang的协程编程体验,又能享受PHP的开发效率。
如果想用原生态的做PHP协程编程,类似鸟哥博客中的调度器必不可少。调度器调度协程执行,协程中断后控制权又回到调度器中。所以调度器应该总是在主(事件)循环中,即CPU不在执行协程,就应当在执行调度器的代码。无协程运行时,调度器应当自我阻塞避免消耗CPU(鸟哥博客中使用了内置的select
系统调用),等待事件到来再执行相应的协程。程序运行期间,除了调度器阻塞,协程在运行过程中不应该调用阻塞API。
在协程编程中,yield
的主要作用是将控制权转让,无需纠结于其返回值(基本上yield
返回的值会在下次执行时直接send
过来)。重点应当关注控制权转让的时机,以及协程的运作方式。
另外需要说明一点,协程和异步没有多大关系,还要看运行环境支撑。常规的PHP运行环境,即使用了promise/coroutine,也还是同步阻塞的。再牛逼的协程框架,sleep
一下也不好使了。作为类比,即使JavaScript不使用promise/async这些技术,也是异步非阻塞的。
通过生成器和Promise,能实现类似于await
PHP의 코루틴 프로그래밍을 다시 살펴보겠습니다. PHP에서는 코루틴 프로그래밍이 생성기를 기반으로 구현됩니다. RecoilPHP
및 Amp
와 같은 코루틴 프레임워크를 사용하는 것이 좋습니다. 이러한 프레임워크에는 이미 스케줄러가 작성되어 있습니다. 생성기 함수를 직접 개발하면 커널이 자동으로 실행을 예약합니다. 함수를 코루틴 모드에서 실행하도록 예약하려면 yield
를 추가하세요. 함수 본문, 즉 Can). 코루틴 프로그래밍에 yield
메서드를 사용하고 싶지 않다면 swoole
또는 그 파생 프레임워크를 권장합니다. 이는 golang과 유사한 코루틴 프로그래밍 경험을 달성하고 PHP의 개발 효율성.
원본 PHP 코루틴 프로그래밍을 사용하려면 Niao Ge의 블로그에 있는 것과 유사한 스케줄러가 필수적입니다. 스케줄러는 코루틴 실행을 예약합니다. 코루틴이 중단되면 제어권이 스케줄러로 돌아갑니다. 따라서 스케줄러는 항상 메인(이벤트) 루프에 있어야 합니다. 즉, CPU가 코루틴을 실행하지 않을 때 스케줄러 코드를 실행해야 합니다. 코루틴 없이 실행할 때 스케줄러는 CPU 소비를 피하기 위해 자체적으로 차단해야 하며(Niao Ge의 블로그에서는 내장된 select
시스템 호출을 사용함) 해당 코루틴을 실행하기 전에 이벤트가 도착할 때까지 기다려야 합니다. 프로그램 실행 중에는 스케줄러 차단을 제외하고 코루틴이 실행 중에 차단 API를 호출해서는 안 됩니다.
yield
의 주요 기능은 반환 값에 대해 걱정하지 않고 제어권을 전달하는 것입니다(기본적으로 yield는 다음 실행 중에 직접 전송
됩니다). 제어권이 전달되는 타이밍과 코루틴이 작동하는 방식에 초점을 맞춰야 합니다. #🎜🎜##🎜🎜# 게다가 코루틴은 비동기와 거의 관련이 없으며 운영 환경 지원에 따라 다르다는 점을 설명해야 합니다. 기존 PHP 운영 환경에서는 promise/coroutine을 사용해도 여전히 동기적으로 차단됩니다. 코루틴 프레임워크가 아무리 훌륭하더라도 sleep
은 사용하기 쉽지 않습니다. 비유하자면, JavaScript가 약속/비동기 기술을 사용하지 않더라도 비동기적이고 비차단적입니다. #🎜🎜##🎜🎜# 제너레이터와 Promise를 통해 await
와 유사한 코루틴 프로그래밍을 구현할 수 있습니다. Github에는 관련 코드가 많이 있으므로 이 글에서는 다루지 않겠습니다. #🎜🎜##🎜🎜#관련 추천: #🎜🎜##🎜🎜##🎜🎜#PHP Medium $_SERVER 상세 소개#🎜🎜##🎜🎜##🎜🎜#PHP의 출력 버퍼링에 대한 자세한 소개, 출력 버퍼링_PHP 튜토리얼
위 내용은 PHP의 코루틴에 대한 자세한 소개(코드)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!