搜尋
首頁後端開發php教程php中協程的詳細介紹(程式碼)
php中協程的詳細介紹(程式碼)Sep 13, 2018 pm 04:57 PM
php協程

本文先介紹了生成器的概念,重點是yield的用法及生成器的介面。協程部分則簡單說了協程的原理,以及PHP協程編程中應注意的事項。

PHP自5.5起引進了生成器(Generator),基於其可實現協程程式設計。本文先回顧生成器,然後再過渡到協程程式設計。

yield與生成器

生成器

生成器是一種資料類型,實作了iterator介面。不能透過new得到生成器實例,也沒有取得生成器實例的靜態方法。得到生成器實例的唯一方法是呼叫生成器函數(包含yield關鍵字的函數)。呼叫生成器函數直接傳回一個生成器對象,生成器運行時函數內的程式碼才開始執行。

先上程式碼直覺地感受一下yield與生成器:

# 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關鍵字,變身為生成函式。呼叫foo不會執行函數體中的任何程式碼,而是傳回一個生成器實例。生成器運行後,foo函數內的程式碼執行,腳本結束。

如其名,生成器可以用來產生資料。只是其生成數據的方式與其他函數不一樣:生成器通過yield返回數據,而非return; yield返回數據後,生成器函數不會銷毀,只是暫停運行,未來可以從暫停處恢復運行;生成器運行一次,(只)返回一個數據,多次運行就返回多個數據;不調用生成器獲取數據,生成器內的代碼就躺著不動,所謂動次打次,說的就是生成器產生資料的樣子。

生成器實作了迭代器接口,取得生成器資料可以用foreach循環或手動current/next/valid。如下程式碼示範資料產生和遍歷:

# generator2.php
function foo() {
  # 返回键值对数据
  yield "key1" => "value1";
  $count = 0;
  while ($count 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關鍵字讓函數擁有這種能力,所以可以用於協程編程。

行程、執行緒與協程

執行緒歸屬於行程,一個行程可有多個執行緒。進程是電腦分配資源的最小單位,執行緒是電腦調度執行的最小單位。行程和執行緒均由作業系統調度。

協程可以看成“用戶狀態的執行緒”,需要使用者程式實現調度。執行緒和進程由作業系統調度「搶佔式」交替運行,協程主動讓出CPU「協商式」交替運行。協程十分的輕量,協程切換不涉及執行緒切換,執行效率高,數目越多,越能體現協程的優勢。

生成器和協程

生成器實現的協程屬於無堆疊協程(stackless coroutine),即生成器函數只有函數幀,運行時附加到呼叫方的棧上執行。有別於功能強大的有棧協程(stackful coroutine),生成器暫停後無法控製程式走向,只能將控制權被動的歸還呼叫者;生成器只能中斷自身,不能中斷整個協程。當然,生成器的好處便是效率高(暫停時只需儲存程式計數器即可),實作簡單。

協程編程

說到PHP中的協程編程,相信大部分人已經看過鳥哥轉載(翻譯)的這篇博文:在PHP中使用協程實現多任務調度。原文作者 nikic 是PHP的核心開發者,生成器功能的倡議者和實作人。想深入了解生成器及基於其的協程編程,nikic關於生成器的RFC和鳥哥網站上的文章必讀。

先看看基於生成器的協程工作方式:協程協作式工作,即協程之間透過主動讓出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的協程編程,相關程式碼在Github上很多,本文不再給出。

相關推薦:

PHP 中$_SERVER 詳細介紹

PHP中的output_buffering詳細介紹,outputbuffering_PHP教學

#

以上是php中協程的詳細介紹(程式碼)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
如何在PHP中使用协程?如何在PHP中使用协程?May 12, 2023 am 08:10 AM

随着传统的多线程模型在高并发场景下的性能瓶颈,协程成为了PHP编程领域的热门话题。协程是一种轻量级的线程,能够在单线程中实现多任务的并发执行。在PHP的语言生态中,协程得到了广泛的应用,比如Swoole、Workerman等框架就提供了对协程的支持。那么,如何在PHP中使用协程呢?本文将介绍一些基本的使用方法以及常见的注意事项,帮助读者了解协程的运作原理,以

如何在Swoole中使用协程实现高并发的swoole_smtp_auth函数如何在Swoole中使用协程实现高并发的swoole_smtp_auth函数Jun 25, 2023 am 08:28 AM

近年来,随着互联网应用的日益普及,各种高并发的场景也越来越常见。在这种情况下,传统的同步I/O方式已经无法满足现代应用对高性能、高并发的需求。因此,协程成为了一种被广泛应用的解决方案。Swoole是一款面向高并发、高性能的PHP网络通信框架,可以轻松实现异步、协程等特性。swoole_smtp_auth函数是其中一个常用的函数,它可以在使用SMTP协议进行邮

满满的干货!全面的介绍Python的协程是如何实现!看懂算你牛!满满的干货!全面的介绍Python的协程是如何实现!看懂算你牛!May 02, 2023 am 10:34 AM

如果你需要访问多个服务来完成一个请求的处理,比如实现文件上传功能时,首先访问Redis缓存,验证用户是否登录,再接收HTTP消息中的body并保存在磁盘上,最后把文件路径等信息写入MySQL数据库中,你会怎么做?首先可以使用阻塞API编写同步代码,直接一步步串行即可,但很明显这时一个线程只能同时处理一个请求。而我们知道线程数是有限制的,有限的线程数导致无法实现上万级别的并发连接,过多的线程切换也抢走了CPU的时间,从而降低了每秒能够处理的请求数量。于是为了达到高并发,你可能会选择一

Swoole进阶:如何使用协程优化数据库查询Swoole进阶:如何使用协程优化数据库查询Jun 15, 2023 pm 09:52 PM

随着Web应用程序的迅速发展,开发者们不仅要关注应用程序的功能和可靠性,还要考虑应用程序的性能。而数据库操作一直是Web应用程序的一个瓶颈之一。传统的数据库查询方式通常是通过多线程或者多进程来实现,这个方法效率低下,而且不容易管理。而Swoole的协程特性可以用来优化数据库查询,并提高应用程序的性能。Swoole是一款PHP的高性能网络框架。它有一个非常重要

Swoole新特性讲解:更快的高速协程HTTP服务器Swoole新特性讲解:更快的高速协程HTTP服务器Jun 15, 2023 pm 08:16 PM

近年来,随着移动互联网、云计算、大数据等新技术的快速发展,越来越多的企业开始使用PHP构建高并发、高性能的Web应用程序。而传统的LAMP(Linux、Apache、MySQL、PHP)架构,难以满足当前互联网快速发展的需求,因此出现了一些新的PHP框架和工具,比如Swoole。Swoole是一个PHP的网络通信框架,具有协程、异步IO、多进程等优势,可以帮

go语言中协程与线程的区别是什么go语言中协程与线程的区别是什么Feb 02, 2023 pm 06:10 PM

区别:1、一个线程可以多个协程,一个进程也可以单独拥有多个协程;2、线程是同步机制,而协程则是异步;3、协程能保留上一次调用时的状态,线程不行;4、线程是抢占式,协程是非抢占式的;5、线程是被分割的CPU资源,协程是组织好的代码流程,协程需要线程来承载运行。

Go 语言中的协程和 select 语句的联系是什么?Go 语言中的协程和 select 语句的联系是什么?Jun 10, 2023 am 09:45 AM

Go语言中的协程和select语句的联系是什么?随着计算机的发展,我们对于并发编程的需求也越来越迫切。然而,传统的并发编程方法——基于线程和锁——也逐渐变得复杂并容易出错。为了解决这些问题,Go语言引入了一种新的并发编程模型——协程。协程是由语言自己调度的轻量级线程,在协程中,代码的执行是基于非抢占式的协作式调度的,换句话说,每个协程都会执行一段代码

协程编程与Swoole实战:实现高并发接口设计协程编程与Swoole实战:实现高并发接口设计Jun 13, 2023 pm 06:39 PM

随着互联网应用的普及,越来越多的应用需要面对高并发的挑战。传统的线程池或进程池方式已经不能满足这种情况下的需求。协程编程技术成为了一种解决高并发问题的有效方式,而Swoole则是目前应用最广泛的协程框架之一。本文将介绍协程编程的基本概念和原理,以及如何使用Swoole框架进行高并发接口设计。我们将以一个简单的Web服务为例,分步骤介绍如何使用协程和Swool

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
3 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版

PhpStorm Mac 版本

PhpStorm Mac 版本

最新(2018.2.1 )專業的PHP整合開發工具

Atom編輯器mac版下載

Atom編輯器mac版下載

最受歡迎的的開源編輯器

SAP NetWeaver Server Adapter for Eclipse

SAP NetWeaver Server Adapter for Eclipse

將Eclipse與SAP NetWeaver應用伺服器整合。

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境