Sợi với một hạt muối


Vì vậy, tôi định viết một bài blog chuyên sâu về cách sử dụng Fiber trong PHP 8.1. Chúng ta sẽ bắt đầu với một ví dụ cơ bản để giải thích chúng từ đầu. Ý tưởng là gửi các yêu cầu HTTP không đồng bộ và xử lý chúng song song bằng cách sử dụng các sợi.

Nhưng khi chơi đùa với họ, tôi biết được rằng RFC không đùa khi nói “API Fiber dự kiến ​​sẽ không được sử dụng trực tiếp trong mã cấp ứng dụng. Fiber cung cấp API kiểm soát luồng cấp thấp, cơ bản để tạo ra các API cấp cao hơn – mức độ trừu tượng sau đó được sử dụng trong mã ứng dụng”.

Vì vậy, thay vì đi theo con đường này và làm cho mọi thứ trở nên quá phức tạp, chúng ta sẽ thảo luận về khái niệm Fiber là gì, tại sao chúng hầu như không thể sử dụng được trong mã ứng dụng và rốt cuộc làm cách nào bạn có thể sử dụng PHP không đồng bộ.

Đầu tiên, một chút về lý thuyết.


Hãy tưởng tượng bạn muốn gửi ba yêu cầu HTTP và xử lý kết quả tổng hợp của chúng. Cách đồng bộ để thực hiện việc này là gửi cái đầu tiên, chờ phản hồi, sau đó gửi cái thứ hai, chờ, v.v.

Hãy biểu diễn luồng chương trình như vậy bằng một biểu đồ dễ hiểu nhất có thể. Bạn cần đọc biểu đồ này từ trên xuống và thời gian càng đi xuống. Mỗi màu đại diện cho một yêu cầu HTTP. Các phần màu của mỗi yêu cầu thể hiện mã PHP thực sự đang chạy, trong đó CPU trên máy chủ của bạn đang hoạt động, các khối trong suốt biểu thị thời gian chờ: yêu cầu cần được gửi qua dây, máy chủ khác cần xử lý và gửi lại . Chỉ khi có phản hồi thì chúng tôi mới có thể làm việc trở lại.

Đây là luồng thực thi đồng bộ: gửi, chờ, xử lý, lặp lại.

Trong thế giới xử lý song song, chúng ta gửi yêu cầu nhưng đừng Chờ đợi. Sau đó, chúng tôi gửi yêu cầu tiếp theo, tiếp theo là yêu cầu khác. Chỉ một sau đó chúng tôi có chờ đợi tất cả các yêu cầu không. Và trong khi chờ đợi, chúng tôi định kỳ kiểm tra xem một trong các yêu cầu của chúng tôi đã được hoàn thành hay chưa. Nếu đúng như vậy thì chúng tôi có thể xử lý ngay lập tức.

Bạn có thể thấy cách tiếp cận như vậy giúp giảm thời gian thực hiện vì chúng tôi đang sử dụng thời gian chờ một cách tối ưu hơn.

Sợi là một cơ chế mới trong PHP 8.1 cho phép bạn quản lý các đường dẫn thực thi song song đó hiệu quả hơn. Điều đó đã có thể thực hiện được bằng cách sử dụng máy phát điện và yieldnhưng sợi là một cải tiến đáng kể vì chúng được thiết kế đặc biệt cho trường hợp sử dụng này.

Bạn sẽ tạo một sợi cho mỗi yêu cầu và tạm dừng sợi sau khi yêu cầu được gửi. Sau khi tạo xong cả ba sợi, bạn lặp lại chúng và tiếp tục từng sợi một. Bằng cách đó, sợi sẽ kiểm tra xem yêu cầu đã được hoàn thành chưa, nếu không nó sẽ tạm dừng lại, nếu không nó có thể xử lý phản hồi và cuối cùng là kết thúc.

Bạn thấy đấy, sợi là một cơ chế để bắt đầu, tạm dừng và tiếp tục luồng thực thi của một phần bị cô lập trong chương trình của bạn. Các sợi còn được gọi là “sợi xanh”: các sợi thực sự tồn tại trong cùng một quy trình. Những luồng đó không được quản lý bởi hệ điều hành mà là thời gian chạy – thời gian chạy PHP trong trường hợp của chúng tôi. Chúng là một cách quản lý hiệu quả về mặt chi phí một số các dạng lập trình song song.

Nhưng hãy lưu ý cách họ không thêm bất cứ thứ gì thực sự không đồng bộ: tất cả các sợi đều hoạt động trong cùng một quy trình PHP và mỗi lần chỉ có một sợi có thể chạy. Đó là quy trình chính lặp lại chúng và kiểm tra chúng trong khi chờ đợi và vòng lặp đó thường được gọi là “vòng lặp sự kiện”.

Phần khó khăn của tính song song không phải là cách bạn lặp qua các sợi quang hoặc bộ tạo hoặc bất kỳ cơ chế nào bạn muốn sử dụng; đó là việc có thể bắt đầu một thao tác, chuyển giao nó cho một dịch vụ bên ngoài và chỉ kiểm tra kết quả khi bạn muốn, theo cách không bị chặn.

Hãy xem, trong các ví dụ trước, chúng ta đã giả định rằng chúng ta có thể gửi một yêu cầu và kiểm tra phản hồi của nó sau khi chúng ta muốn, nhưng điều đó thực sự không dễ dàng như người ta tưởng.

Đúng vậy: hầu hết các hàm của PHP xử lý I/O đều không được tích hợp sẵn chức năng không chặn này. Trên thực tế, chỉ có một số chức năng có thể thực hiện được và việc sử dụng chúng khá cồng kềnh.

Có một ví dụ về ổ cắm, có thể được đặt ở chế độ không chặn, như sau:

($read, $write) = stream_socket_pair(
    STREAM_PF_UNIX,
    STREAM_SOCK_STREAM,
    STREAM_IPPROTO_IP
);
 
stream_set_blocking($read, false);
stream_set_blocking($write, false);

Bằng cách sử dụng stream_socket_pair(), hai ổ cắm được tạo có thể được sử dụng để liên lạc hai chiều. Và như bạn có thể thấy, chúng có thể được đặt ở chế độ không chặn bằng cách sử dụng stream_set_blocking().

Giả sử chúng ta muốn triển khai ví dụ của mình bằng cách gửi ba yêu cầu. Chúng tôi có thể sử dụng các ổ cắm để làm như vậy, nhưng chúng tôi cần phải tự mình triển khai giao thức HTTP trên đó. Đó chính xác là những gì nox7 đã làm, một người dùng đã chia sẻ một bằng chứng nhỏ về khái niệm trên Reddit để chỉ ra cách gửi yêu cầu HTTP GET bằng cáp quang và ổ cắm. Bạn có thực sự muốn quan tâm đến việc làm như vậy trong mã ứng dụng của mình không?

Ít nhất, đối với tôi, câu trả lời là “không”. Đó chính xác là những gì RFC đã cảnh báo; Tôi không giận chuyện đó. Thay vào đó, chúng tôi khuyến khích sử dụng một trong các khung công tác không đồng bộ hiện có: Amp hoặc ReactPHP.

Ví dụ: với ReactPHP, chúng ta có thể viết một cái gì đó như thế này:

$loop = React\EventLoop\Factory::create();

$browser = new Clue\React\Buzz\Browser($loop);

$promises = (
    $browser->get('https://example.com/1'),
    $browser->get('https://example.com/2'),
    $browser->get('https://example.com/3'),
);

$responses = Block\awaitAll($promises, $loop);

Đó là một ví dụ tốt hơn so với việc tạo kết nối ổ cắm theo cách thủ công. Và đó chính là ý nghĩa của RFC: các nhà phát triển ứng dụng không cần phải lo lắng về sợi, đó là chi tiết triển khai cho các khung như Amp hoặc ReactPHP.

Tuy nhiên, điều đó đưa chúng ta đến câu hỏi: lợi ích của sợi là gì so với những gì chúng ta có thể làm với máy phát điện? Vâng RFC giải thích nó theo cách này:

Không giống như Trình tạo không có ngăn xếp, mỗi Fiber có ngăn xếp lệnh gọi riêng, cho phép chúng tạm dừng trong các lệnh gọi hàm lồng nhau sâu. Một hàm khai báo một điểm gián đoạn (tức là gọi Fiber::suspend()) không cần thay đổi kiểu trả về của nó, không giống như hàm sử dụng năng suất phải trả về một phiên bản Generator.

Fiber có thể bị treo trong bất kỳ lệnh gọi hàm nào, bao gồm cả những lệnh được gọi từ bên trong máy ảo PHP, chẳng hạn như các hàm được cung cấp cho array_map hoặc các phương thức được foreach gọi trên đối tượng Iterator.

Rõ ràng rằng Fiber là một cải tiến đáng kể, cả về cú pháp lẫn tính linh hoạt. Nhưng chúng vẫn chưa là gì so với Go, chẳng hạn như với “goroutines” của nó.

Vẫn còn thiếu nhiều chức năng để PHP async trở thành xu hướng phổ biến mà không cần đến các framework và Fiber là một bước đi đúng hướng, nhưng chúng tôi vẫn chưa đạt được điều đó.

Nhận thấy một tpyo? Bạn có thể gửi PR để sửa nó. Nếu bạn muốn cập nhật về những gì đang diễn ra trên blog này, bạn có thể Theo dõi danh sách gửi thư của tôi: gửi email đến [email protected] và tôi sẽ thêm bạn vào danh sách.

Vì vậy, có điều đó. Thực sự không có nhiều điều để nói về Fiber nếu bạn không phải là người duy trì Amp, ReactPHP hoặc khung PHP không đồng bộ nhỏ hơn. Có lẽ nhiều khung hoặc thư viện hơn sẽ bắt đầu kết hợp chúng?

Trong khi đó, còn có Swoole – một tiện ích mở rộng PHP thực sự sửa đổi một số chức năng cốt lõi để không bị chặn. Bản thân Swoole là một dự án của Trung Quốc và thường không được ghi chép đầy đủ bằng tiếng Anh, nhưng gần đây Laravel đã công bố việc tích hợp bên đầu tiên với nó. Có lẽ đây là chiến lược tốt hơn khi chuyển PHP sang mô hình không đồng bộ hơn: tùy chọn tích hợp Swoole hoặc các tiện ích mở rộng khác với các khung như Laravel và Symfony?

Sẽ rất thú vị để xem tương lai sẽ mang lại điều gì!



Leave a Comment

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *

Scroll to Top