AMPHP คือชุดไลบรารีที่ขับเคลื่อนด้วยเหตุการณ์สำหรับ PHP ซึ่งได้รับการออกแบบโดยคำนึงถึงไฟเบอร์และการทำงานพร้อมกัน amphp/amp
ให้อนาคตและการยกเลิกโดยเฉพาะเป็นพื้นฐานพื้นฐานสำหรับการเขียนโปรแกรมแบบอะซิงโครนัส ตอนนี้เรากำลังใช้ Revolt แทนการจัดส่งการใช้งานลูปเหตุการณ์ด้วย amphp/amp
Amp ใช้ไฟเบอร์จำนวนมากที่มาพร้อมกับ PHP 8.1 เพื่อเขียนโค้ดแบบอะซิงโครนัสเหมือนกับโค้ดบล็อกแบบซิงโครนัส ตรงกันข้ามกับเวอร์ชันก่อนหน้า ไม่จำเป็นต้องมีคอร์รูทีนหรือคอลแบ็กที่ใช้ตัวสร้าง เช่นเดียวกับเธรด แต่ละไฟเบอร์มี call stack ของตัวเอง แต่ไฟเบอร์จะถูกกำหนดเวลาร่วมกันโดยลูปเหตุการณ์ ใช้ Ampasync()
เพื่อเรียกใช้สิ่งต่าง ๆ พร้อมกัน
ตามเนื้อผ้า PHP เป็นไปตามรูปแบบการดำเนินการตามลำดับ เอ็นจิ้น PHP ดำเนินการหนึ่งบรรทัดตามลำดับ อย่างไรก็ตาม บ่อยครั้งโปรแกรมประกอบด้วยโปรแกรมย่อยอิสระหลายโปรแกรมซึ่งสามารถดำเนินการพร้อมกันได้
หากคุณสอบถามฐานข้อมูล คุณจะส่งแบบสอบถามและรอการตอบกลับจากเซิร์ฟเวอร์ฐานข้อมูลในลักษณะบล็อก เมื่อคุณได้รับคำตอบแล้ว คุณก็สามารถเริ่มทำสิ่งต่อไปได้ แทนที่จะนั่งเฉยและไม่ทำอะไรขณะรอ เราสามารถส่งการสืบค้นฐานข้อมูลถัดไปหรือทำการเรียก HTTP ไปยัง API ได้แล้ว มาใช้เวลาที่เรามักจะใช้ในการรอ I/O กันดีกว่า!
Revolt อนุญาตให้มีการดำเนินการ I/O พร้อมกันดังกล่าว เรารักษาภาระการรับรู้ให้ต่ำโดยหลีกเลี่ยงการโทรกลับ API ของเราสามารถใช้ได้เหมือนกับไลบรารีอื่นๆ ยกเว้นว่าสิ่งต่างๆ ยัง ทำงานพร้อมกันได้ เนื่องจากเราใช้ I/O ที่ไม่ปิดกั้นภายใต้ประทุน รันสิ่งต่าง ๆ ไปพร้อม ๆ กันโดยใช้ Ampasync()
และรอผลลัพธ์โดยใช้ Future::await()
ที่ไหนและเมื่อไหร่ที่คุณต้องการ!
มีเทคนิคต่างๆ มากมายสำหรับการนำการทำงานพร้อมกันใน PHP ไปใช้ในช่วงหลายปีที่ผ่านมา เช่น การโทรกลับและตัวสร้างที่จัดส่งใน PHP 5 วิธีการเหล่านี้ประสบปัญหา "ฟังก์ชันของคุณมีสีอะไร" ซึ่งเราแก้ไขได้ด้วยการจัดส่ง Fibers ด้วย PHP 8.1 อนุญาตให้มีการทำงานพร้อมกันกับสแต็กการโทรอิสระหลายรายการ
ไฟเบอร์ได้รับการกำหนดเวลาร่วมกันโดย event-loop ซึ่งเป็นสาเหตุที่เรียกอีกอย่างว่าคอร์รูทีน สิ่งสำคัญคือต้องเข้าใจว่ามีโครูทีนเพียงตัวเดียวที่ทำงานอยู่ ณ เวลาใดก็ตาม ส่วนโครูทีนอื่นๆ ทั้งหมดจะถูกระงับชั่วคราวในระหว่างนี้
คุณสามารถเปรียบเทียบคอร์รูทีนกับคอมพิวเตอร์ที่ทำงานหลายโปรแกรมโดยใช้ CPU คอร์เดียว แต่ละโปรแกรมจะมีช่วงเวลาในการดำเนินการ อย่างไรก็ตาม Coroutines ไม่ได้ถูกยึดเอาเสียก่อน พวกเขาไม่ได้รับช่วงเวลาที่แน่นอน พวกเขาต้องยกเลิกการควบคุมลูปเหตุการณ์โดยสมัครใจ
ฟังก์ชัน I/O การบล็อกใดๆ จะบล็อกกระบวนการทั้งหมดในขณะที่รอ I/O คุณจะต้องการหลีกเลี่ยงพวกเขา หากคุณยังไม่ได้อ่านคู่มือการติดตั้ง โปรดดูตัวอย่าง Hello World ที่แสดงให้เห็นถึงผลกระทบของฟังก์ชันการบล็อก ไลบรารีที่ AMPHP เตรียมไว้ให้หลีกเลี่ยงการบล็อก I/O
แพ็คเกจนี้สามารถติดตั้งเป็นการพึ่งพานักแต่งเพลง
composer require amphp/amp
หากคุณใช้ไลบรารีนี้ เป็นไปได้มากว่าคุณต้องการกำหนดเวลากิจกรรมโดยใช้ Revolt ซึ่งคุณควรกำหนดให้แยกใช้ แม้ว่าจะถูกติดตั้งเป็นการขึ้นต่อกันโดยอัตโนมัติก็ตาม
composer require revolt/event-loop
แพ็คเกจเหล่านี้จัดเตรียมองค์ประกอบพื้นฐานสำหรับแอปพลิเคชันแบบอะซิงโครนัส / พร้อมกันใน PHP เรามีการสร้างแพ็คเกจมากมายนอกเหนือจากสิ่งเหล่านี้ เช่น
amphp/byte-stream
ให้สตรีมที่เป็นนามธรรมamphp/socket
จัดเตรียมเลเยอร์ซ็อกเก็ตสำหรับ UDP และ TCP รวมถึง TLSamphp/parallel
ให้การประมวลผลแบบขนานเพื่อใช้ CPU หลายคอร์และลดการดำเนินการบล็อกamphp/http-client
ที่ให้บริการไคลเอ็นต์ HTTP/1.1 และ HTTP/2amphp/http-server
ที่ให้บริการแอปพลิเคชันเซิร์ฟเวอร์ HTTP/1.1 และ HTTP/2amphp/mysql
และ amphp/postgres
สำหรับการเข้าถึงฐานข้อมูลแบบไม่บล็อกแพ็คเกจนี้ต้องใช้ PHP 8.1 หรือใหม่กว่า ไม่จำเป็นต้องขยายเวลา!
จำเป็นต้องใช้ส่วนขยายเฉพาะในกรณีที่แอปของคุณจำเป็นต้องมีการเชื่อมต่อซ็อกเก็ตพร้อมกันจำนวนมาก โดยปกติแล้วขีดจำกัดนี้จะกำหนดค่าตัวอธิบายไฟล์ได้สูงสุด 1,024 ตัว
Coroutines เป็นฟังก์ชันที่ขัดจังหวะได้ ใน PHP สามารถใช้งานได้โดยใช้ไฟเบอร์
หมายเหตุ เวอร์ชันก่อนหน้าของ Amp ใช้ตัวสร้างเพื่อจุดประสงค์ที่คล้ายกัน แต่ไฟเบอร์สามารถถูกขัดจังหวะได้ทุกที่ใน call stack ซึ่งทำให้ต้นแบบก่อนหน้านี้ เช่น
Ampcall()
ไม่จำเป็น
ในเวลาใดก็ตาม มีเพียงเส้นใยเดียวเท่านั้นที่ทำงานอยู่ เมื่อโครูทีนหยุดทำงานชั่วคราว การดำเนินการของโครูทีนจะถูกขัดจังหวะชั่วคราว ทำให้สามารถรันงานอื่นๆ ได้ การดำเนินการจะกลับมาอีกครั้งเมื่อตัวจับเวลาหมดลง การดำเนินการสตรีมเป็นไปได้ หรือ Future
ที่รอคอยเสร็จสิ้น
ระบบกันสะเทือนระดับต่ำและการเริ่มต้นใหม่ของโครูทีนได้รับการจัดการโดย Suspension
API ของ Revolt
<?php
require __DIR__ . ' /vendor/autoload.php ' ;
use Revolt EventLoop ;
$ suspension = EventLoop:: getSuspension ();
EventLoop:: delay ( 5 , function () use ( $ suspension ): void {
print ' ++ Executing callback created by EventLoop::delay() ' . PHP_EOL ;
$ suspension -> resume ( null );
});
print ' ++ Suspending to event loop... ' . PHP_EOL ;
$ suspension -> suspend ();
print ' ++ Script end ' . PHP_EOL ;
การโทรกลับที่ลงทะเบียนไว้ใน Revolt event-loop จะถูกเรียกใช้เป็น coroutines โดยอัตโนมัติ และสามารถระงับได้อย่างปลอดภัย นอกเหนือจาก event-loop API แล้ว Ampasync()
ยังสามารถใช้เพื่อเริ่ม call stack อิสระได้
<?php
use function Amp delay ;
require __DIR__ . ' /vendor/autoload.php ' ;
Amp async ( function () {
print ' ++ Executing callback passed to async() ' . PHP_EOL ;
delay ( 3 );
print ' ++ Finished callback passed to async() ' . PHP_EOL ;
});
print ' ++ Suspending to event loop... ' . PHP_EOL ;
delay ( 5 );
print ' ++ Script end ' . PHP_EOL ;
Future
เป็นวัตถุที่แสดงถึงผลลัพธ์สุดท้ายของการดำเนินการแบบอะซิงโครนัส มีสามรัฐ:
อนาคตที่เสร็จสมบูรณ์จะคล้ายคลึงกับค่าที่ส่งคืน ในขณะที่อนาคตที่มีข้อผิดพลาดจะคล้ายคลึงกับการส่งข้อยกเว้น
วิธีหนึ่งในการเข้าถึง API แบบอะซิงโครนัสคือการใช้การเรียกกลับที่ถูกส่งเมื่อการดำเนินการเริ่มต้นและถูกเรียกเมื่อดำเนินการเสร็จสิ้น:
doSomething ( function ( $ error , $ value ) {
if ( $ error ) {
/* ... */
} else {
/* ... */
}
});
วิธีการโทรกลับมีข้อเสียหลายประการ
นั่นคือสิ่งที่อนาคตเข้ามามีบทบาท เป็นตัวยึดตำแหน่งสำหรับผลลัพธ์ที่ส่งคืนเช่นเดียวกับค่าที่ส่งคืนอื่นๆ ผู้โทรมีทางเลือกในการรอผลลัพธ์โดยใช้ Future::await()
หรือลงทะเบียนการโทรกลับหนึ่งรายการหรือหลายรายการ
try {
$ value = doSomething ()-> await ();
} catch (...) {
/* ... */
}
ในแอปพลิเคชันที่ใช้งานพร้อมกัน จะมีฟิวเจอร์สหลายรายการ ซึ่งคุณอาจต้องการรอทั้งหมดหรือเพียงอันแรก
AmpFutureawait($iterable, $cancellation)
กำลังรอวัตถุ Future
ทั้งหมดของ iterable
หากข้อผิดพลาดของอินสแตนซ์ Future
รายการใดรายการหนึ่ง การดำเนินการจะถูกยกเลิกพร้อมกับข้อยกเว้นนั้น มิฉะนั้น ผลลัพธ์จะเป็นคีย์การจับคู่อาร์เรย์จากอินพุต iterable
จนถึงค่าที่สมบูรณ์
ตัวรวม await()
มีประสิทธิภาพอย่างมากเนื่องจากช่วยให้คุณสามารถดำเนินการแบบอะซิงโครนัสหลายรายการพร้อมกันได้ ลองดูตัวอย่างการใช้ amphp/http-client
เพื่อดึงทรัพยากร HTTP หลายรายการพร้อมกัน:
<?php
use Amp Future ;
use Amp Http Client HttpClientBuilder ;
use Amp Http Client Request ;
$ httpClient = HttpClientBuilder:: buildDefault ();
$ uris = [
" google " => " https://www.google.com " ,
" news " => " https://news.google.com " ,
" bing " => " https://www.bing.com " ,
" yahoo " => " https://www.yahoo.com " ,
];
try {
$ responses = Future await ( array_map ( function ( $ uri ) use ( $ httpClient ) {
return Amp async ( fn () => $ httpClient -> request ( new Request ( $ uri , ' HEAD ' )));
}, $ uris ));
foreach ( $ responses as $ key => $ response ) {
printf (
" %s | HTTP/%s %d %s n" ,
$ key ,
$ response -> getProtocolVersion (),
$ response -> getStatus (),
$ response -> getReason ()
);
}
} catch ( Exception $ e ) {
// If any one of the requests fails the combo will fail
echo $ e -> getMessage (), "n" ;
}
AmpFutureawaitAnyN($count, $iterable, $cancellation)
เหมือนกับ await()
ยกเว้นว่าจะยอมรับข้อผิดพลาดแต่ละรายการ ผลลัพธ์จะถูกส่งกลับเมื่ออินสแตนซ์ $count
เสร็จสิ้นใน iterable
ได้สำเร็จ ค่าที่ส่งคืนคืออาร์เรย์ของค่า แต่ละคีย์ในอาร์เรย์คอมโพเนนต์จะถูกเก็บรักษาไว้จาก iterable
ที่ส่งผ่านไปยังฟังก์ชันเพื่อการประเมิน
AmpFutureawaitAll($iterable, $cancellation)
กำลังรอฟิวเจอร์สทั้งหมดและส่งกลับผลลัพธ์เป็นอาร์เรย์ [$errors, $values]
AmpFutureawaitFirst($iterable, $cancellation)
จะแกะ Future
ที่เสร็จสมบูรณ์ครั้งแรก ไม่ว่าจะเสร็จสมบูรณ์แล้วหรือเกิดข้อผิดพลาด
AmpFutureawaitAny($iterable, $cancellation)
จะแกะ Future
ที่สำเร็จครั้งแรกสำเร็จ
อนาคตสามารถสร้างได้หลายวิธี โค้ดส่วนใหญ่จะใช้ Ampasync()
ซึ่งรับฟังก์ชันและรันเป็น coroutine ในไฟเบอร์อื่น
บางครั้งอินเทอร์เฟซกำหนดให้ส่งคืน Future
แต่ผลลัพธ์จะพร้อมใช้งานทันที เช่น เนื่องจากถูกแคชไว้ ในกรณีเหล่านี้ Future::complete(mixed)
และ Future::error(Throwable)
สามารถใช้เพื่อสร้าง Future
ที่เสร็จสมบูรณ์ได้ทันที
หมายเหตุ
DeferredFuture
API ที่อธิบายด้านล่างเป็น API ขั้นสูงที่แอปพลิเคชันจำนวนมากอาจไม่ต้องการ ใช้Ampasync()
หรือตัวผสมแทนเมื่อเป็นไปได้
AmpDeferredFuture
มีหน้าที่รับผิดชอบในการทำ Future
ที่รอดำเนินการให้เสร็จสิ้น คุณสร้าง AmpDeferredFuture
และใช้เมธอด getFuture
เพื่อส่งคืน AmpFuture
ให้กับผู้โทร เมื่อผลลัพธ์พร้อม คุณจะเสร็จสิ้น Future
ที่ผู้โทรถือไว้โดยใช้ข้อมูล complete
หรือ error
ใน DeferredFuture
ที่เชื่อมโยง
final class DeferredFuture
{
public function getFuture (): Future ;
public function complete ( mixed $ value = null );
public function error ( Throwable $ throwable );
}
คำเตือน หากคุณกำลังส่งวัตถุ
DeferredFuture
ไปรอบๆ คุณอาจกำลังทำอะไรผิด สิ่งเหล่านี้ควรจะเป็นสถานะภายในของการดำเนินการของคุณ
คำเตือน คุณไม่สามารถสร้างอนาคตด้วยอนาคตอื่นได้ ใช้
Future::await()
ก่อนที่จะเรียกDeferredFuture::complete()
ในกรณีเช่นนี้
ต่อไปนี้คือตัวอย่างง่ายๆ ของตัวสร้างค่าอะซิงโครนัส asyncMultiply()
ที่สร้าง DeferredFuture
และส่งคืน Future
ที่เกี่ยวข้องไปยังผู้เรียก
<?php // Example async producer using DeferredFuture
use Revolt EventLoop ;
function asyncMultiply ( int $ x , int $ y ): Future
{
$ deferred = new Amp DeferredFuture ;
// Complete the async result one second from now
EventLoop:: delay ( 1 , function () use ( $ deferred , $ x , $ y ) {
$ deferred -> complete ( $ x * $ y );
});
return $ deferred -> getFuture ();
}
$ future = asyncMultiply ( 6 , 7 );
$ result = $ future -> await ();
var_dump ( $ result ); // int(42)
ทุกการดำเนินการที่สนับสนุนการยกเลิกจะยอมรับอินสแตนซ์ของ Cancellation
เป็นอาร์กิวเมนต์ การยกเลิกคือออบเจ็กต์ที่อนุญาตให้ตัวจัดการการลงทะเบียนสมัครรับคำขอยกเลิกได้ ออบเจ็กต์เหล่านี้ถูกส่งผ่านไปยังการดำเนินการย่อยหรือต้องได้รับการจัดการโดยการดำเนินการเอง
$cancellation->throwIfRequested()
สามารถใช้เพื่อล้มเหลวในการดำเนินการปัจจุบันด้วย CancelledException
เมื่อมีการร้องขอการยกเลิก แม้ว่า throwIfRequested()
จะทำงานได้ดี แต่การดำเนินการบางอย่างอาจต้องการสมัครสมาชิกด้วยการโทรกลับแทน พวกเขาสามารถทำได้โดยใช้ Cancellation::subscribe()
เพื่อสมัครรับคำขอยกเลิกที่อาจเกิดขึ้น
ผู้เรียกสร้าง Cancellation
โดยใช้การใช้งานอย่างใดอย่างหนึ่งด้านล่าง
หมายเหตุ การยกเลิกเป็นเพียงคำแนะนำเท่านั้น ตัวแก้ไข DNS อาจเพิกเฉยต่อคำขอยกเลิกหลังจากส่งแบบสอบถามไปแล้ว เนื่องจากต้องดำเนินการตอบกลับต่อไปและยังสามารถแคชได้ ไคลเอนต์ HTTP อาจดำเนินการร้องขอ HTTP ที่ใกล้เสร็จแล้วต่อไปเพื่อใช้การเชื่อมต่อซ้ำ แต่อาจยกเลิกการตอบสนองการเข้ารหัสแบบก้อนเนื่องจากไม่สามารถรู้ได้ว่าการดำเนินการต่อมีราคาถูกกว่าการยกเลิกจริงหรือไม่
TimeoutCancellations
จะยกเลิกตัวเองโดยอัตโนมัติหลังจากครบจำนวนวินาทีที่ระบุ
request ( " ... " , new Amp TimeoutCancellation ( 30 ));
SignalCancellation
จะยกเลิกตัวเองโดยอัตโนมัติหลังจากที่กระบวนการปัจจุบันได้รับสัญญาณที่ระบุแล้ว
request ( " ... " , new Amp SignalCancellation ( SIGINT ));
DeferredCancellation
ช่วยให้สามารถยกเลิกด้วยตนเองด้วยการเรียกเมธอด นี่เป็นวิธีที่แนะนำหากคุณต้องการลงทะเบียนการติดต่อกลับแบบกำหนดเองที่ใดที่หนึ่ง แทนที่จะจัดส่งการใช้งานของคุณเอง เฉพาะผู้เรียกเท่านั้นที่สามารถเข้าถึง DeferredCancellation
และสามารถยกเลิกการดำเนินการได้โดยใช้ DeferredCancellation::cancel()
$ deferredCancellation = new Amp DeferredCancellation ();
// Register some custom callback somewhere
onSomeEvent ( fn () => $ deferredCancellation -> cancel ());
request ( " ... " , $ deferredCancellation -> getCancellation ());
NullCancellation
จะไม่ถูกยกเลิก การยกเลิกมักเป็นทางเลือก ซึ่งโดยปกติจะดำเนินการโดยการทำให้พารามิเตอร์เป็นโมฆะ เพื่อหลีกเลี่ยงการป้องกันเช่น if ($cancellation)
คุณสามารถใช้ NullCancellation
แทนได้
$ cancellation ??= new NullCancellationToken ();
CompositeCancellation
จะรวมออบเจ็กต์การยกเลิกอิสระหลายรายการเข้าด้วยกัน หากการยกเลิกใดๆ เหล่านี้ถูกยกเลิก CompositeCancellation
จะถูกยกเลิกไปด้วย
amphp/amp
เป็นไปตามข้อกำหนดการกำหนดเวอร์ชันเชิงความหมายของ semver เช่นเดียวกับแพ็คเกจ amphp
อื่นๆ ทั้งหมด
แพ็คเกจที่เข้ากันได้ควรใช้หัวข้อ amphp
บน GitHub
หากคุณพบปัญหาใดๆ ที่เกี่ยวข้องกับความปลอดภัย โปรดส่งอีเมลมาที่ [email protected]
แทนการใช้ตัวติดตามปัญหา
ใบอนุญาตเอ็มไอที (MIT) โปรดดู LICENSE
สำหรับข้อมูลเพิ่มเติม