banner
Jan 21, 2023
650 Views

[Phần 1] Reactor pattern 101

Written by
banner

Ở bài viết này chúng ta sẽ nói qua về các khải niệm chính tạo nên reactor pattern như kiến trúc đơn luồng và non-blocking I/O.

Ở phần 2 chúng ta sẽ ghép nối các kiến thức ở bài này thành reactor pattern.

Blocking I/O

Khi lập trình kiểu Blocking I/O thông thường thì các tác vụ liên quan tới I/O sẽ chặn các tác vụ phía sau cho tới khi tác vụ đó hoàn thành, các tác vụ này có thể mất vài milli giây hoặc cả tiếng đồng hồ. Ví dụ chúng ta có mã giả như sau:

Bạn dễ dàng nhận thấy rằng khi lập trình theo kiểu Blocking I/O này sẽ khiến 1 thread không thể phục vụ nhiều requests từ người dùng. Vì thế thông thường web server sẽ phải dùng 1 trong các phương pháp sau:

  • Bật thêm luồng (thread).
  • Bật thêm tiến trình (process) khác.
  • Lấy 1 thread đang không dùng ở thread pool.

Việc này phải làm cho từng requests mới để việc block I/O ở thread này không ảnh hưởng tới thread khác.

Blocking I/O

Bạn có thể thấy ở ảnh trên server sẽ tốn thời gian chờ khi tương tác với I/O ví dụ DB hoặc file. Các thread cũng ngốn khá nhiều tài nguyên hệ hống như RAM/ CPU. Việc giữ các thread quá lâu ở trạng thái chờ có vẻ không giúp hệ thống hoạt động hiệu quả nhất là khi luôn phải giữ tỉ lệ 1:1 giữa requests-threads. Khi phải đối mặt với bài toán c10k hệ thống sẽ thực sự gặp rất nhiều vấn đề.

Non-blocking I/O

Ngoài blocking I/O, các hệ thống hiện đại cũng hỗ trợ non-blocking I/O. Khi hoạt động ở trạng thái này hệ thống sẽ trả về dữ liệu ngay lập tức mà không chờ I/O read/ write. Nếu không có kết quả lập tức để trả về thì hệ thống trả về 1 giá trị được định nghĩa trước thông báo là không có dữ liệu để trả về ở thời điểm hiện tại.

Phương pháp cơ bản nhất để hệ thống thực hiện non-blocking I/O là liên tục lặp lại việc kiểm tra xem có dữ liệu trả về chưa (hay gọi là busy-waiting). Đoạn mã giả sau cho bạn thấy điều đó được thực hiện như thế nào:

Với phương pháp đơn giản này hệ thống đã có thể xử lý nhiều I/O trong cùng 1 thread nhưng nếu bạn giải quyết như thế hệ thống vẫn hoạt động không hiệu quả. CPU sẽ tốn rất nhiều thời gian để lặp và kiểm tra các tài nguyên chưa sẵn sàng gây ra lãng phí tài nguyên hệ thống.

Event demultiplexing

busy-waiting chắc chắn không phải là cách tối ưu khi xử lý non-blocking I/O. Hiểu được điều này hầu hết các hệ điều hành hiện đại cung cấp một giải pháp có sẵn là synchronous event demultiplexer (tách sự kiện đa kênh một cách đồng bộ) hay còn gọi là event notification interface (giao diện thông báo sự kiện) như sau:

  • Linux epoll
  • Mac kqueue
  • Windows I/O Completion Port IOCP

Thành phần này thu gom và sắp xếp các sự kiện I/O thành hàng đợi từ các tài nguyên đang cần được theo dõi, sau đó ngăn lại và chờ tới khi có các sự kiện mới từ các tài nguyên đó để xử lý. Ví dụ như đoạn mã giả dưới đây dùng synchronous event demultiplexer để xử lý 2 tài nguyên khác nhau

Giải thích các bước trên như sau:

  1. Thêm tài nguyên vào 1 kiểu dữ liệu nào đó, mỗi tài nguyên ứng với 1 hành động cụ thể khi tài nguyên đó sẵn sàng, ví dụ ở đây là READ.
  2. event notifier được cài đặt với 1 nhóm các tài nguyên cần theo dõi. Chỗ này là 1 lệnh đồng-bộ và sẽ bị ngăn xử lý tới khi có bất kỳ tài nguyên nào sẵn sàng để READ. Khi tài nguyên sẵn sàng, event demultiplexer được trả về và 1 loạt các sự kiện sẽ sẵn sàng để xử lý.
  3. Xử lý các sự kiện được trả về từ event demultiplexer. Ở chỗ này các tài nguyên ứng với mỗi sự kiện sẽ sẵn sàng để READ và sẽ không bị block khi xử lý. Khi tất cả các sự kiện được xử lý thì luồng xử lý này lại bị chặn ở [2] cho tới khi tài nguyên sẵn sàng. Bước này gọi là event-loop.

Bạn có thể thấy với cách giải quyết trên chúng ta có thể xử lý nhiều I/O với chỉ 1 thread mà không cần dùng busy-waiting. Như ảnh sau cho chúng ta thấy cách web server xử lý nhiều connect dùng synchronous event demultiplexer bằng 1 thread.

Non-blocking I/O

Ảnh trên cho chúng ta hiểu cách mà hệ thống đơn luồng vẫn có thể xử lý nhiều connect và I/O đồng thời thay vì phải chia thành các luồng độc lập. Phương pháp này không chỉ giúp các thread được tối ưu giảm thời gian chờ mà còn thay đổi cách tiếp cận của lập trình viên khi xử lý các bài toán song song vì rõ ràng khi chỉ dùng 1 thread các vấn đề về race conditions và đồng bộ giữa các tiến trình sẽ giảm hẳn giúp chúng ta đơn giản hóa việc xử lý các bài toán song song.

Source:

https://subscription.packtpub.com/book/web-development/9781783287314/1/ch01lvl1sec09/the-reactor-pattern

https://www.calebpitan.com/blog/understanding-the-event-loop-and-io-operations#stuff-can-sometimes-go-south

https://dzone.com/articles/understanding-reactor-pattern-thread-based-and-eve

Article Categories:
dev
banner

Leave a Reply

Your email address will not be published. Required fields are marked *