So sánh việc sử dụng select() và fork()

Thread và Posix Thread

Trong các bàitrước, chúng ta đã được học về một thành phầnrất quan trọng củahệ điều hành là tiến trình vàcách hệ điều hành Linux tạo ra cũng nhưquản lý tiến trình. Trong chương này, chúng ta sẽ tìm hiểu một thành phầnquan trọng tiếp theo và có liên quan rất chặt chẽ với tiến trình là luồng. Luồng với tên tiếng anh là Thread được sử dụng rất nhiều trong lập trình Linux nhằmthực hiện đa nhiệm cho tiến trình và tối ưu hóa performance của hệ thống. Vì thread là thuật ngữ đã quá phổ biến và xuất hiện trong tất cả các cuốn sách và các websitevề Linux, nên từ phần sau của khóa họcchúng tasẽ dùng thuật ngữ thread thay cho luồng.

Hãy cùng bắt đầu bằng việctìm hiểu tổng quanvề cách hoạt động của thread, cách một thread được tạo ra cũng như kết thúc, từ đó có cái nhìn tổng thểkhi nào thì nên dùng và không nên dùng thread. Tiếp theo, chúng ta sẽ làm quen vớicác hàm API của thư viện Posix thread [pthread],mộtthư viện tiêu chuẩn cho lập trìnhthread được sử dụng thống trị trong lập trìnhLinux.

Why asynchronous non-blocking I/O??

Chi tiết ở The C10K problem

Ngắn dọn là do OS Thread quá tốn kém [memory, CPU time context switching]. Các mô hình blocking với 1 thread / 1 request trở nên không còn hiệu quả.

EM sử dụng select[] của Linux trong quá trình chạy qua các vòng lặp để kiểm tra đầu vào mới trên các file description. Một lý do phổ biến để sử dụng EventMachine là trường hợp khi bạn có rất nhiều hoạt động I/O và bạn không muốn đối phó với các hoạt động này bằng tay. Các chuỗi xử lý thủ công có thể khó khăn hoặc thường quá đắt so với quan điểm sử dụng tài nguyên. Với EM, bạn có thể xử lý nhiều yêu cầu HTTP với một luồng đơn theo mặc định.

# em.rb EM.run do EM.add_timer[1] do puts 'sleeping...' EM.system['sleep 1'] { puts "woke up!" } puts 'continuing...' end EM.add_timer[3] { EM.stop } end $ ruby em.rb sleeping... continuing... woke up!

Ví dụ trên cho thấy làm thế nào để chạy mã không đồng bộ bằng cách thực thi EM.system [hoạt động I/O] và chạy một block như một callback cái mà sẽ được thực hiện khi lệnh hệ thống đã hoàn tất. Ưu điểm:

  • Có thể đạt được hiệu suất tuyệt vời cho các ứng dụng được nối mạng chậm như máy chủ web và proxy với một luồng duy nhất.
  • Nó cho phép bạn tránh các chương trình đa luồng phức tạp, những bất lợi được mô tả ở trên.

Nhược điểm:

  • Mỗi toán tử I/O nên được hỗ trợ EM không đồng bộ. Điều này có nghĩa là bạn nên sử dụng các phiên bản cụ thể của hệ thống, DB adapter, HTTP client ... có thể dẫn đến các phiên bản vá lỗi monkey-patched, thiếu hỗ trợ và các tùy chọn hạn chế.
  • Công việc thực hiện trong luồng chính cho mỗi vòng lặp nên nhỏ. Ngoài ra, có thể sử dụng Defer, nó thực hiện mã trong các luồng riêng biệt từ thread pool, tuy nhiên, nó có thể dẫn đến các vấn đề đa luồng thảo luận trước đó.
  • Khó để thực hiện các hệ thống phức tạp vì các lỗi xử lý và callbacks. Callback Hell cũng có thể có trong Ruby, nhưng nó có thể được ngăn chặn bằng Fibres.
  • EventMachine chính là một sự phụ thuộc rất lớn: 17K LOC [dòng mã] trong Ruby và 10K LOC trong C ++.

Ví dụ:

  • Goliath - một máy chủ không đồng bộ đơn luồng.
  • AMQP - RabbitMQ client. Tuy nhiên, người tạo ra gem này đề xuất sử dụng non-EM-based phiên bản Bunny. Lưu ý rằng các công cụ di chuyển sang cài đặt EM-less là một xu hướng chung. Ví dụ: người sáng tạo ActionCable đã quyết định sử dụng nio4r cấp thấp, người sáng tạo sinatra-synchrony viết lại nó với Celluloid, v.v.

Fibers

Fibers là các primitives nhẹ trong thư viện chuẩn Ruby, có thể được tạm dừng, tiếp tục và lập lịch bằng tay. Chúng khá giống với ES6 Generator nếu bạn đã quen thuộc với JavaScript. Có thể chạy hàng chục nghìn Fibers trong một luồng đơn. Thông thường, Fibers được sử dụng với EventMachine để tránh callback và làm cho code nhìn đồng bộ. Vì vậy, đoạn code sau đây:

EventMachine.run do page = EM::HttpRequest.new['//google.ca/'].get page.errback { puts "Google is down" } page.callback { url = '//google.ca/search?q=universe.com' about = EM::HttpRequest.new[url].get about.errback { ... } about.callback { ... } } end

Có thể được viết lại như sau:

EventMachine.run do Fiber.new { page = http_get['//www.google.com/'] if page.response_header.status == 200 about = http_get['//google.ca/search?q=universe.com'] # ... else puts "Google is down" end }.resume end def http_get[url] current_fiber = Fiber.current http = EM::HttpRequest.new[url].get http.callback { current_fiber.resume[http] } http.errback { current_fiber.resume[http] } Fiber.yield end

Vì vậy, về cơ bản, Fiber#yield trở lại ngữ cảnh đã khôi phục lại Fiber và trả về giá trị đã được truyền cho Fiber#resume. Ưu điểm:

  • Fibers cho phép bạn đơn giản hóa mã không đồng bộ bằng cách thay thế các callbacks lồng nhau.

Nhược điểm:

  • Không thực sự giải quyết các vấn đề concurrency.
  • Chúng ít khi được sử dụng trực tiếp trong code cấp ứng dụng.

Ví dụ:

  • Em-synchrony - một thư viện, được viết bởi Ilya Grigorik, một kỹ sư tại Google, giúp tích hợp EventMachine với Fibres cho các client khác nhau như MySQL2, Mongo, Memcached ...

Kết luận

Hãy chọn một mô hình concurrency tùy thuộc vào nhu cầu của bạn. Ví dụ, cần phải chạy CPU và bộ nhớ mã chuyên sâu và có đủ nguồn lực - sử dụng processes. Phải thực hiện nhiều hoạt động I/O như yêu cầu HTTP - sử dụng threads. Cần phải mở rộng quy mô tối đa - sử dụng EventMachine.

Tham khảo: //engineering.universe.com/introduction-to-concurrency-models-with-ruby-part-i-550d0dbb970


Understand Nodejs

  • Report

Nodejs hiện nay đang rất nổi như một xu thế công nghệ mới. Với sự mạnh mẽ, cấu trúc khác biệt nên Nodejs đã tạo nên một cơn sốt thời gian qua: nhanh, tốn ít tài nguyên, đáp ứng được lượng request lớn. Đặc biệt nó đáp ứng được tính realtime của ứng dụng. Mình là một người khá tò mò và thích khám phá điều mới lạ. Lúc đầu đọc qua tài liệu và ví dụ với các cụm từ event-driven, non-blocking, asynchonous, single thread... mình bị choáng. Vì các khái niệm này quá trìu tượng và khó hiểu. Đó là động lực giúp mình đi tìm câu trả lời? Điêu gì tạo nên sức mạnh của Node.js.

I/O

I/O là quá trình giao tiếp [lấy dữ liệu vào, trả dữ liệu ra] giữa một hệ thống thông tin và môi trường bên ngoài. Với CPU, thậm chí mọi giao tiếp dữ liệu với bên ngoài cấu trúc chip như việc nhập/ xuất dữ liệu với memory [RAM] cũng là tác vụ I/O. Trong kiến trúc máy tính, sự kết hợp giữa CPU và bộ nhớ chính [main memory – RAM] được coi là bộ não của máy tính, mọi thao tác truyền dữ liệu với bộ đôi CPU/Memory, ví dụ đọc ghi dữ liệu từ ổ cứng đều được coi là tác vụ I/O.

Do các thành phần bên trong kiến trúc phụ thuộc vào dữ liệu từ các thành phần khác, mà tốc độ giữa các thành phần này là khác nhau, khi một thành phần hoạt động không theo kịp thành phần khác, khiến thành phần khác phải rảnh rỗi vì không có dữ liệu làm việc, thành phần chậm chạp kia trở thành một bottle-neck, kéo lùi hiệu năng của toàn bộ hệ thống.

Dựa theo các thành phần của kiến trúc máy tính hiện đại, tốc độ thực hiện tiến trình phụ thuộc:

  • CPU Bound: Tốc độ thực hiện tiến trình bị giới hạn bởi tốc độ xử lý của CPU
  • Memory Bound: Tốc độ thực hiện tiến trình bị giới hạn bởi dung lượng khả dụng và tốc độ truy cập của bộ nhớ
  • Cache Bound: Tốc độ thực hiện tiến trình bị giới hạn bởi số lượng ô nhớ và tốc độ của các thanh cache khả dụng
  • I/O Bound: Tốc độ thực hiện tiến trình bị giới hạn bởi tốc độ của các tác vụ IO
1
I/O Bound < Memory Bound < Cache Bound < CPU Bound

Do tốc độ I/O thường rất chậm so với các thành phần còn lại, bottle-neck thường xuyên xảy ra ở đây. Người ta thường xét đến I/O Bound và CPU Bound, cố gắng đưa các process bị giới hạn bởi I/O bound về CPU bound để tận dụng tối đa hiệu năng.

Video liên quan

Bài Viết Liên Quan

Chủ Đề