Elixir - Quy trình
Trong Elixir, tất cả mã chạy bên trong các quy trình. Các quy trình được cách ly với nhau, chạy đồng thời với nhau và giao tiếp thông qua truyền tin nhắn. Không nên nhầm lẫn các quy trình của Elixir với các quy trình của hệ điều hành. Các quy trình trong Elixir cực kỳ nhẹ về bộ nhớ và CPU (không giống như các luồng trong nhiều ngôn ngữ lập trình khác). Do đó, không có gì lạ khi có hàng chục hoặc thậm chí hàng trăm nghìn tiến trình chạy đồng thời.
Trong chương này, chúng ta sẽ tìm hiểu về các cấu trúc cơ bản để tạo ra các quy trình mới, cũng như gửi và nhận thông điệp giữa các quy trình khác nhau.
Chức năng đẻ trứng
Cách dễ nhất để tạo một quy trình mới là sử dụng spawnchức năng. Cácspawnchấp nhận một chức năng sẽ được chạy trong quy trình mới. Ví dụ -
pid = spawn(fn -> 2 * 2 end)
Process.alive?(pid)
Khi chương trình trên được chạy, nó tạo ra kết quả sau:
false
Giá trị trả về của hàm sinh sản là PID. Đây là số nhận dạng duy nhất cho quy trình và vì vậy nếu bạn chạy mã phía trên PID của mình, mã sẽ khác. Như bạn có thể thấy trong ví dụ này, quá trình đã chết khi chúng tôi kiểm tra xem nó còn sống hay không. Điều này là do quá trình sẽ thoát ngay sau khi nó chạy xong chức năng đã cho.
Như đã đề cập, tất cả các mã Elixir đều chạy bên trong các quy trình. Nếu bạn chạy chức năng tự, bạn sẽ thấy PID cho phiên hiện tại của bạn -
pid = self
Process.alive?(pid)
Khi chương trình trên được chạy, nó tạo ra kết quả sau:
true
Thông qua
Chúng tôi có thể gửi tin nhắn đến một quy trình với send và nhận chúng với receive. Hãy để chúng tôi chuyển một tin nhắn đến quy trình hiện tại và nhận nó trên cùng một quy trình.
send(self(), {:hello, "Hi people"})
receive do
{:hello, msg} -> IO.puts(msg)
{:another_case, msg} -> IO.puts("This one won't match!")
end
Khi chương trình trên được chạy, nó tạo ra kết quả sau:
Hi people
Chúng tôi đã gửi một thông báo đến quy trình hiện tại bằng cách sử dụng chức năng gửi và chuyển nó đến PID của chính mình. Sau đó, chúng tôi xử lý tin nhắn đến bằng cách sử dụngreceive chức năng.
Khi một tin nhắn được gửi đến một quy trình, tin nhắn đó sẽ được lưu trữ trong process mailbox. Khối nhận đi qua hộp thư của quy trình hiện tại để tìm kiếm một thư phù hợp với bất kỳ mẫu nào đã cho. Khối nhận hỗ trợ bảo vệ và nhiều mệnh đề, chẳng hạn như trường hợp.
Nếu không có thư nào trong hộp thư khớp với bất kỳ mẫu nào, quy trình hiện tại sẽ đợi cho đến khi có thư phù hợp. Thời gian chờ cũng có thể được chỉ định. Ví dụ,
receive do
{:hello, msg} -> msg
after
1_000 -> "nothing after 1s"
end
Khi chương trình trên được chạy, nó tạo ra kết quả sau:
nothing after 1s
NOTE - Thời gian chờ bằng 0 có thể được đưa ra khi bạn đã mong đợi thư ở trong hộp thư.
Liên kết
Hình thức sinh sản phổ biến nhất trong Elixir thực sự là thông qua spawn_linkchức năng. Trước khi xem một ví dụ với spawn_link, hãy để chúng tôi hiểu điều gì sẽ xảy ra khi một quy trình không thành công.
spawn fn -> raise "oops" end
Khi chương trình trên được chạy, nó tạo ra lỗi sau:
[error] Process #PID<0.58.00> raised an exception
** (RuntimeError) oops
:erlang.apply/2
Nó ghi lại một lỗi nhưng quá trình sinh sản vẫn đang chạy. Điều này là do các quy trình bị cô lập. Nếu chúng ta muốn sự thất bại trong một quá trình truyền sang một quá trình khác, chúng ta cần liên kết chúng. Điều này có thể được thực hiện vớispawn_linkchức năng. Chúng ta hãy xem xét một ví dụ để hiểu giống nhau -
spawn_link fn -> raise "oops" end
Khi chương trình trên được chạy, nó tạo ra lỗi sau:
** (EXIT from #PID<0.41.0>) an exception was raised:
** (RuntimeError) oops
:erlang.apply/2
Nếu bạn đang chạy cái này trong iexshell thì shell xử lý lỗi này không thoát. Nhưng nếu bạn chạy trước bằng cách tạo một tệp script và sau đó sử dụngelixir <file-name>.exs, quy trình gốc cũng sẽ bị hạ xuống do lỗi này.
Các quy trình và liên kết đóng một vai trò quan trọng khi xây dựng hệ thống chịu lỗi. Trong các ứng dụng Elixir, chúng tôi thường liên kết các quy trình của mình với người giám sát, người này sẽ phát hiện khi nào một quy trình chết và bắt đầu một quy trình mới ở vị trí của nó. Điều này chỉ có thể thực hiện được vì các quy trình được tách biệt và không chia sẻ bất kỳ thứ gì theo mặc định. Và vì các quy trình được tách biệt, không có cách nào mà một quy trình bị lỗi sẽ làm hỏng hoặc làm hỏng trạng thái của quy trình khác. Trong khi các ngôn ngữ khác sẽ yêu cầu chúng tôi bắt / xử lý các ngoại lệ; trong Elixir, chúng tôi thực sự ổn khi để các quy trình không thành công vì chúng tôi mong đợi người giám sát khởi động lại hệ thống của chúng tôi một cách chính xác.
Tiểu bang
Nếu bạn đang xây dựng một ứng dụng yêu cầu trạng thái, ví dụ, để giữ cấu hình ứng dụng của bạn hoặc bạn cần phân tích cú pháp một tệp và giữ nó trong bộ nhớ, bạn sẽ lưu trữ nó ở đâu? Chức năng quy trình của Elixir có thể hữu ích khi làm những việc như vậy.
Chúng ta có thể viết các tiến trình lặp lại vô hạn, duy trì trạng thái và gửi và nhận tin nhắn. Ví dụ: chúng ta hãy viết một mô-đun khởi động các quy trình mới hoạt động như một kho lưu trữ khóa-giá trị trong một tệp có tênkv.exs.
defmodule KV do
def start_link do
Task.start_link(fn -> loop(%{}) end)
end
defp loop(map) do
receive do
{:get, key, caller} ->
send caller, Map.get(map, key)
loop(map)
{:put, key, value} ->
loop(Map.put(map, key, value))
end
end
end
Lưu ý rằng start_link hàm bắt đầu một quá trình mới chạy loopchức năng, bắt đầu với một bản đồ trống. Cácloopsau đó chức năng chờ tin nhắn và thực hiện hành động thích hợp cho từng tin nhắn. Trong trường hợp của một:gettin nhắn, nó sẽ gửi lại một tin nhắn cho người gọi và các cuộc gọi lặp lại, để chờ một tin nhắn mới. Trong khi:put thông điệp thực sự gọi loop với một phiên bản mới của bản đồ, với khóa và giá trị đã cho được lưu trữ.
Bây giờ chúng ta hãy chạy phần sau -
iex kv.exs
Bây giờ bạn nên ở trong iexvỏ sò. Để kiểm tra mô-đun của chúng tôi, hãy thử các bước sau:
{:ok, pid} = KV.start_link
# pid now has the pid of our new process that is being
# used to get and store key value pairs
# Send a KV pair :hello, "Hello" to the process
send pid, {:put, :hello, "Hello"}
# Ask for the key :hello
send pid, {:get, :hello, self()}
# Print all the received messages on the current process.
flush()
Khi chương trình trên được chạy, nó tạo ra kết quả sau:
"Hello"