Computer >> Máy Tính >  >> Lập trình >> Ruby

Lặn sâu đồng thời:Đa quy trình

Trong một bài viết trước của Ruby Magic về Làm chủ đồng thời, chúng tôi đã giới thiệu về ba phương pháp đạt được đồng thời có sẵn cho chúng tôi với tư cách là các nhà phát triển Ruby. Bài viết này là bài đầu tiên trong loạt bài gồm ba phần, nơi chúng ta đi sâu vào từng phương pháp.

Mục đầu tiên: Nhiều quy trình . Với phương pháp này, một quy trình tổng thể tự chia thành nhiều quy trình công nhân. Quy trình công nhân thực hiện công việc thực tế, trong khi chủ quản lý công nhân.

Mã nguồn đầy đủ được sử dụng trong các ví dụ trong bài viết này có sẵn trên GitHub, vì vậy bạn có thể tự thử nghiệm với nó.

Hãy xây dựng hệ thống trò chuyện!

Xây dựng một hệ thống trò chuyện là một cách tốt để đi sâu vào đồng thời. Chúng tôi sẽ cần một thành phần máy chủ của hệ thống trò chuyện có thể duy trì kết nối với nhiều máy khách. Điều này sẽ cho phép chúng tôi phân phối các thông điệp mà nó nhận được từ một máy khách đến tất cả các máy khách được kết nối khác.

Máy chủ trò chuyện của chúng tôi đang chạy ở tab bên trái. Có hai ứng dụng trò chuyện đang chạy ở các tab bên phải. Bất kỳ tin nhắn nào được gửi bởi một khách hàng sẽ được tất cả các khách hàng khác nhận được.

Ứng dụng trò chuyện

Bài viết này tập trung vào máy chủ trò chuyện, nhưng để giao tiếp với nó, trước tiên, chúng tôi sẽ cần một ứng dụng khách trò chuyện. Đoạn mã sau sẽ là ứng dụng khách rất đơn giản của chúng tôi. (Có thể tìm thấy một ví dụ đầy đủ hơn trên GitHub.)

# client.rb
# $ ruby client.rb
require 'socket'
client = TCPSocket.open(ARGV[0], 2000)
 
Thread.new do
  while line = client.gets
    puts line.chop
  end
end
 
while input = STDIN.gets.chomp
  client.puts input
end

Máy khách mở kết nối TCP với máy chủ chạy trên cổng 2000. Khi được kết nối, nó tạo ra một chuỗi sẽ puts bất kỳ thứ gì mà máy chủ gửi, vì vậy cuộc trò chuyện sẽ hiển thị trong đầu ra của thiết bị đầu cuối. Cuối cùng, có một vòng lặp trong khi gửi bất kỳ dòng nào bạn nhập đến máy chủ, dòng này sẽ gửi đến tất cả các máy khách được kết nối khác.

Máy chủ trò chuyện

Trong ví dụ này, một máy khách kết nối với một máy chủ trò chuyện để giao tiếp với các máy khách khác. Đối với cả ba phương pháp tiếp cận đồng thời, chúng tôi sẽ sử dụng cùng một máy chủ TCP từ thư viện chuẩn của Ruby.

# server_processes.rb
# $ ruby server_processes.rb
require 'socket'
 
puts 'Starting server on port 2000'
 
server = TCPServer.open(2000)

Cho đến thời điểm này, mã là giống nhau cho cả ba mô hình đồng thời. Sau đó, máy chủ trò chuyện trong mọi mô hình sẽ cần xử lý hai tình huống:

  1. Chấp nhận các kết nối mới từ khách hàng.
  2. Nhận tin nhắn từ khách hàng và gửi chúng cho tất cả các khách hàng khác.

Máy chủ trò chuyện đa quy trình

Để xử lý hai kịch bản này bằng máy chủ trò chuyện đa quy trình, chúng tôi sẽ tạo một quy trình cho mỗi kết nối máy khách. Quá trình này sẽ xử lý tất cả các tin nhắn được gửi và nhận cho khách hàng đó. Chúng tôi có thể tạo các quy trình này bằng cách phân tách quy trình máy chủ ban đầu.

Quy trình rèn

Khi bạn gọi phương thức fork, nó sẽ tạo một bản sao của quy trình hiện tại với cùng trạng thái mà quy trình đang ở.

Một quy trình được chia nhỏ có id quy trình riêng và sẽ được hiển thị riêng trong một công cụ như top hoặc Giám sát hoạt động. Nó trông giống như thế này:

Quy trình bạn bắt đầu được gọi là quy trình chính và các quy trình được tách ra khỏi quy trình chính được gọi là quy trình công nhân.

Vì các quy trình worker mới được chia nhỏ này thực sự là các quy trình riêng biệt, chúng tôi không thể chia sẻ bộ nhớ giữa chúng và quy trình chính. Chúng ta cần một cái gì đó để giao tiếp giữa họ.

Ống Unix

Để giao tiếp giữa các tiến trình, chúng ta sẽ sử dụng các đường ống Unix. Một đường ống Unix thiết lập một luồng byte hai chiều giữa hai quy trình và bạn có thể sử dụng nó để gửi dữ liệu từ quy trình này sang quy trình khác. May mắn thay, Ruby cung cấp một lớp bao bọc xung quanh các đường ống này nên chúng tôi không cần phải phát minh lại bánh xe.

Trong ví dụ sau, chúng tôi thiết lập một đường ống trong Ruby –với một đầu đọc và một kết thúc ghi– và chúng tôi fork quy trình tổng thể. Mã trong khối được chuyển tới fork đang chạy trong quá trình chia nhỏ. Quá trình ban đầu tiếp tục sau khối này. Sau đó, chúng tôi viết một thông báo tới quy trình ban đầu từ quy trình đã phân nhánh.

reader, writer = IO.pipe
 
fork do
  # This is running in the forked process.
  writer.puts 'Hello from the forked process'
end
 
# This is running in the original process, it will puts the
# message from the forked process.
puts reader.gets

Sử dụng đường ống, chúng tôi có thể giao tiếp giữa các quy trình riêng biệt mặc dù các quy trình hoàn toàn cách biệt với nhau.

Việc triển khai máy chủ trò chuyện

Đầu tiên, chúng tôi thiết lập một mảng để theo dõi các đường ống cho tất cả khách hàng và "người viết" của họ (phần cuối bằng văn bản của đường ống), vì vậy chúng tôi có thể giao tiếp với khách hàng. Sau đó, chúng tôi đảm bảo rằng tất cả các tin nhắn đến từ các khách hàng sẽ được gửi đến tất cả các khách hàng khác.

client_writers = []
master_reader, master_writer = IO.pipe
 
write_incoming_messages_to_child_processes(master_reader, client_writers)

Bạn có thể tìm thấy việc triển khai write_incoming_messages_to_child_processes trên GitHub nếu bạn muốn xem chi tiết về cách nó hoạt động.

Chấp nhận kết nối mới

Chúng tôi sẽ cần phải chấp nhận các kết nối đến và thiết lập các đường ống. Người viết mới sẽ được đẩy lên client_writers mảng. Quá trình chính sẽ có thể lặp qua mảng và gửi thông báo đến từng quy trình công nhân bằng cách ghi vào đường dẫn của nó.

Sau đó, chúng tôi phân nhánh quy trình chính và mã trong quy trình công nhân đã phân tách sẽ xử lý kết nối máy khách.

Vòng lặp
loop do
  while socket = server.accept
    # Create a client reader and writer so that the master
    # process can write messages back to us.
    client_reader, client_writer = IO.pipe
 
    # Put the client writer on the list of writers so the
    # master process can write to them.
    client_writers.push(client_writer)
 
    # Fork child process, everything in the fork block
    # only runs in the child process.
    fork do
      # Handle connection
    end
  end
end

Xử lý kết nối máy khách

Chúng tôi cũng cần xử lý kết nối máy khách.

Quá trình phân tách bắt đầu bằng cách lấy biệt hiệu từ máy khách (máy khách gửi biệt hiệu theo mặc định). Sau đó, nó bắt đầu một chuỗi trong write_incoming_messages_to_client lắng nghe thông báo từ quy trình chính.

Cuối cùng, quá trình phân nhánh bắt đầu một vòng lặp lắng nghe các tin nhắn đến và gửi chúng đến quá trình chính. Quy trình chính đảm bảo quy trình công nhân khác nhận được thông báo.

nickname = read_line_from(socket)
puts "#{Process.pid}: Accepted connection from #{nickname}"
 
write_incoming_messages_to_client(nickname, client_reader, socket)
 
# Read incoming messages from the client.
while incoming = read_line_from(socket)
  master_writer.puts "#{nickname}: #{incoming}"
end
 
puts "#{Process.pid}: Disconnected #{nickname}"

Hệ thống trò chuyện hoạt động

Bây giờ toàn bộ hệ thống trò chuyện hoạt động! Nhưng như bạn thấy, việc viết một chương trình sử dụng đa xử lý là khá phức tạp và sử dụng nhiều tài nguyên. Ưu điểm là nó rất mạnh mẽ. Nếu một trong các tiến trình con bị lỗi, phần còn lại của hệ thống sẽ tiếp tục hoạt động. Bạn có thể thử điều đó bằng cách chạy mã ví dụ và chạy kill -9 <process-id> trên một trong các quy trình (bạn có thể tìm thấy id quy trình trong đầu ra nhật ký của máy chủ).

Trong bài viết tiếp theo, chúng tôi sẽ triển khai cùng một hệ thống trò chuyện chỉ bằng cách sử dụng các chuỗi, vì vậy chúng tôi có thể chạy một máy chủ với các tính năng tương tự chỉ bằng một quy trình và ít bộ nhớ hơn.