Multiprocessing, Multithreading, Coroutine và PHP - P1

Chào các bạn, hôm nay chúng ta sẽ cùng nói một chút về xử lý bất đồng bộ và các vấn đề của nó

Ví dụ chúng ta có đoạn code sau

$items = Http::get('https://example.com/data.json')
foreach($items as $item)
{
    //do something
}

Đối với các ngôn ngữ đồng bộ như PHP, Java, đoạn code trên sẽ được thực thi tuần tự từ trên xuống dưới. Kết quả sẽ có dạng

Kết nối đến example.com lần 1
Xử lý kết quả lần 1
Kết nối đến example.com lần 2
Xử lý kết quả lần 2

Chúng ta đều thấy có một vấn đề rằng, việc gọi tác vụ I/O (Gửi HTTP Request đến example.com) có thể tốn nhiều thời gian. Nếu có thể xử lý song song trong thời gian gửi Request thì sẽ có thể nâng performance hệ thống lên khá nhiều.

Multiprocessing

Các process được khởi tạo và quản lý bởi hệ điều hành OS. Các process độc lập với nhau về tài nguyên hệ thống (Ví dụ bộ nhớ).
Giả sử chúng ta có một chiếc máy tính với 1 core CPU, việc khởi chạy nhiều process có thể giúp xử lý đoạn code phía trên như sau

Khởi tạo process 1
Kết nối đến example.com lần 1
Khởi tạo process 2
Kết nối đến example.com lần 2
Quay về process 1, nếu xong thì xử lý kết quả lần 1
Quay về process 2, nếu xong thì xử lý kết quả lần 2

Cách xử lý như trên gọi là Concurrency (Mình gọi là đồng thời). Hiện tại hầu hết máy tính đều có nhiều hơn 1 core CPU, nên các tác vụ process có thể được khởi tạo trên nhiều CPU khác nhau. Khi đó chúng ta sẽ có Xử lý song song (Parallel). Mô tả khác nhau cơ bản giữa ConcurrencyParallel các bạn có thể tham khảo hình dưới đây:
alt text
Nguồn hình mình lấy từ medium
Vấn đề với process là việc khởi tạo, switching context giữa các process là tương đối tốn tài nguyên (Vì các process là độc lập). Đối với PHP, chúng ta có PHP-FPM và process pool để phần nào giảm tải việc này, tuy nhiên, vẫn có những cách tối ưu hơn để giải quyết vấn đề process.

Multithreading

Các thread cũng được khởi tạo và quản lý lập lịch bởi OS. Tuy nhiên, khác với process, các thread trên cùng process sẽ dùng chung một không gian bộ nhớ.
Đối với thread, hầu hết các tác vụ đều là Concurrency (Giống như chúng ta đã đề cập bên trên). OS sẽ chịu trách nhiệm cấp phát tài nguyên, lập lịch và switching context giữa các thread. Ví dụ với đoạn code phía trên, OS sẽ slice các khoảng thời gian nhỏ, ví dụ 100, 200ms để chuyển đổi giữa các thread khác nhau. Một process có thể khởi chạy nhiều thread

Vấn đề với thread là việc sử dụng chung tài nguyên có thể dẫn đến những tranh chấp nếu chúng ta bất cẩn. Khởi tạo nhiều thread trên nhiều process, switching context giữa các thread cũng tương đối tốn kém tài nguyên.

Coroutine

Coroutine là kết hợp giữa co-operate và routine. Ở đây các tác vụ sẽ được coi là routine, và được chạy Concurrency. Coroutine cũng giống như thread, khi các routine có thể chia sẻ chung tài nguyên, và có cơ chế giao tiếp với nhau.
Khác biệt lớn nhất giữa coroutine và process/thread, là coroutine được sinh ra và quản lý hoàn toàn bởi runtime của ngôn ngữ lập trình (Ví dụ Go / Java / PHP). Việc switching context giữa các routine được xử lý tự động bởi runtime, hoặc developer cũng có thể kiếm soát quá trình này.

Ví dụ ở đầu của chúng ta sẽ có một chút thay đổi

$items = Http::get('https://example.com/data.json')
//yield (nhường quyền switch context lại cho runtime)
foreach($items as $item)
{
    //do something
}

$item2s = Http::get('https://example.com/data2.json')
//yield (nhường quyền switch context lại cho runtime)
foreach($item2s as $item2)
{
    //do something
}

Các bạn sẽ thấy có những đoạn yield. Đó chính là lúc runtime (hoặc developer) đánh dấu việc có thể nhường lại quyền chuyển đổi giữa các routine để tiếp tục khởi chạy các routine khác. Chúng ta có thể diễn giải

Khởi tạo routine  1
Kết nối đến example.com lần 1
Yield, routine 1 nhường quyền lại runtime
Khởi tạo routine  2
Kết nối đến example.com lần 2
Yield routine 2 nhường quyền lại runtime
Quay về routine 1, nếu xong thì xử lý kết quả lần 1
Quay về routine 2, nếu xong thì xử lý kết quả lần 2

Đối với cá nhân mình, coroutine có nét khá giống với callback handler trên JS. Không biết cái này có đúng không, nếu sai mong các bạn comment giải thích thêm.

Ở đây việc lập lịch, đã không còn nằm trong tay OS nữa mà là runtime. Điều này dẫn chúng ta đến một khái niệm khá phổ biến: Non-blocking I/O. Các tác vụ I/O xuất hiện rất nhiều trong các ứng dụng: Khởi tạo kết nối database, gửi HTTP Request ... Về cơ bản, những tác vụ chúng ta gửi đầu vào (Input) và chờ đợi đầu ra (Output) đều có thể gọi là tác vụ I/O. Việc sử dụng Coroutine sẽ yêu cầu các thư viện, các đoạn code hỗ trợ Non-blocking (cơ chế trả quyền switch context cho runtime), nếu không chúng ta sẽ ko có Concurrency và quay về với việc chạy đồng bộ truyền thống.

Lợi thế lớn của coroutine đó là việc được quản lý bởi runtime, nên chi phí tài nguyên tốn rất ít. Một coroutine chỉ tốn khoảng 2KB để khởi tạo (Thread có thể được phân phối từ 1 đến vài MB khác nhau). Chúng ta cũng có thể khởi tạo hàng nghìn coroutine trên một thread, giúp ứng dụng của chúng ta có hiệu suất cao.

Vấn đề với coroutine là việc share chung tài nguyên cũng cần tốn thêm quản lý khi lập trình. Việc yield giữa các routine cũng cần cẩn trọng để tránh có những kết quả không mong muốn.

Đối với PHP, nếu muốn chuyển sang coroutine, ví dụ Swoole, chi phí chuyển đổi cũng lớn vì sẽ phải thay thế các thư viện sang non-blocking IO.

Bất đồng bộ PHP

PHP đã từ bỏ khái niệm thread. Chúng ta sẽ dựa hoàn toàn vào Process để triển khai Concurrency. Bên cạnh thread, coroutine PHP cũng phát triển với rất nhiều thư viện hỗ trợ như Swoole, amp, roadrunner. Mình sẽ bàn đến việc áp dụng coroutine vào PHP trong phần sau của bài viết này.
Cám ơn các bạn đã đọc bài viết này. Nếu có gì sai sót, mong mọi người chỉ bảo thêm cho mình :D.

Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

White

Nguyễn Thế Huy

11 bài viết.
63 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Cùng một tác giả
White
32 8
Hi cả nhà, đây là bài viết đầu tiên của mình trên Kipalog nên có gì không hay mong các bạn thông cảm :D Realtime là gì ? Như chúng ta đều đã biế...
Nguyễn Thế Huy viết hơn 3 năm trước
32 8
White
28 6
Xin chào mọi người :D Trong bài viết này mình sẽ trình bày một cách cơ bản để ứng dụng kỹ thuật Http Live Streaming (HLS) để play video trên web, ...
Nguyễn Thế Huy viết 3 năm trước
28 6
White
27 6
Bài toán Machine Learning và những ứng dụng của nó đang ngày càng trở nên nổi bật trong những năm trở lại đây. Với sự phát triển thần tốc của cấu ...
Nguyễn Thế Huy viết hơn 2 năm trước
27 6
Bài viết liên quan
White
4 2
Bash script to fast serve Laravel project Lười gõ dòng lệnh quá nên tạo ra cái script để gõ nhanh :D laravelstart.sh /bin/bash if z "$1" ] ...
Vũ Hoàng Chung viết 4 năm trước
4 2
Male avatar
9 1
Để bắt đầu làm thêm của riêng bạn thì ban đầu bạn phải có một theme trắng ( Blank theme ) để bắt đầu Theme trắng là gồm có các thư mục và file cơ b...
Doan Van Manh viết hơn 5 năm trước
9 1
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


White
{{userFollowed ? 'Following' : 'Follow'}}
11 bài viết.
63 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!