Elixir - กระบวนการ
ใน Elixir รหัสทั้งหมดจะทำงานภายในกระบวนการ กระบวนการจะแยกออกจากกันทำงานพร้อมกันและสื่อสารกันผ่านข้อความ ไม่ควรสับสนกระบวนการของ Elixir กับกระบวนการของระบบปฏิบัติการ กระบวนการใน Elixir มีน้ำหนักเบามากในแง่ของหน่วยความจำและ CPU (ไม่เหมือนกับเธรดในภาษาโปรแกรมอื่น ๆ ) ด้วยเหตุนี้จึงไม่ใช่เรื่องแปลกที่จะมีกระบวนการทำงานพร้อมกันเป็นหมื่นหรือหลายแสน
ในบทนี้เราจะเรียนรู้เกี่ยวกับโครงสร้างพื้นฐานสำหรับการวางไข่กระบวนการใหม่ตลอดจนการส่งและรับข้อความระหว่างกระบวนการต่างๆ
ฟังก์ชัน Spawn
วิธีที่ง่ายที่สุดในการสร้างกระบวนการใหม่คือการใช้ไฟล์ spawnฟังก์ชัน spawnยอมรับฟังก์ชั่นที่จะทำงานในกระบวนการใหม่ ตัวอย่างเช่น -
pid = spawn(fn -> 2 * 2 end)
Process.alive?(pid)
เมื่อรันโปรแกรมข้างต้นจะให้ผลลัพธ์ดังนี้ -
false
ค่าส่งกลับของฟังก์ชันวางไข่คือ PID นี่คือตัวระบุเฉพาะสำหรับกระบวนการดังนั้นหากคุณรันโค้ดเหนือ PID ของคุณก็จะแตกต่างกัน ดังที่คุณเห็นในตัวอย่างนี้กระบวนการนี้จะตายเมื่อเราตรวจสอบว่ายังมีชีวิตอยู่หรือไม่ เนื่องจากกระบวนการนี้จะออกทันทีที่รันฟังก์ชันที่กำหนดเสร็จสิ้น
ดังที่ได้กล่าวไปแล้วรหัส Elixir ทั้งหมดจะทำงานภายในกระบวนการ หากคุณเรียกใช้ฟังก์ชัน self คุณจะเห็น PID สำหรับเซสชันปัจจุบันของคุณ -
pid = self
Process.alive?(pid)
เมื่อเรียกใช้โปรแกรมข้างต้นจะให้ผลลัพธ์ดังนี้ -
true
ข้อความผ่าน
เราสามารถส่งข้อความไปยังกระบวนการด้วย send และรับด้วย receive. ให้เราส่งข้อความไปยังกระบวนการปัจจุบันและรับในขั้นตอนเดียวกัน
send(self(), {:hello, "Hi people"})
receive do
{:hello, msg} -> IO.puts(msg)
{:another_case, msg} -> IO.puts("This one won't match!")
end
เมื่อรันโปรแกรมข้างต้นจะให้ผลลัพธ์ดังนี้ -
Hi people
เราส่งข้อความไปยังกระบวนการปัจจุบันโดยใช้ฟังก์ชันส่งและส่งต่อไปยัง PID ของตนเอง จากนั้นเราจัดการข้อความขาเข้าโดยใช้ไฟล์receive ฟังก์ชัน
เมื่อข้อความถูกส่งไปยังกระบวนการข้อความจะถูกเก็บไว้ในไฟล์ process mailbox. บล็อกการรับจะผ่านกล่องจดหมายกระบวนการปัจจุบันที่ค้นหาข้อความที่ตรงกับรูปแบบที่กำหนด บล็อกการรับรองรับยามและหลายมาตราเช่นกรณี
หากไม่มีข้อความในกล่องเมลที่ตรงกับรูปแบบใด ๆ กระบวนการปัจจุบันจะรอจนกว่าข้อความที่ตรงกันจะมาถึง นอกจากนี้ยังสามารถระบุระยะหมดเวลาได้ ตัวอย่างเช่น,
receive do
{:hello, msg} -> msg
after
1_000 -> "nothing after 1s"
end
เมื่อรันโปรแกรมข้างต้นจะให้ผลลัพธ์ดังนี้ -
nothing after 1s
NOTE - สามารถกำหนดระยะหมดเวลาเป็น 0 ได้เมื่อคุณคาดหวังว่าข้อความจะอยู่ในกล่องเมลแล้ว
ลิงค์
รูปแบบการวางไข่ที่พบบ่อยที่สุดใน Elixir เกิดขึ้นจริงผ่านทาง spawn_linkฟังก์ชัน ก่อนที่จะดูตัวอย่าง spawn_link ให้เราเข้าใจว่าจะเกิดอะไรขึ้นเมื่อกระบวนการล้มเหลว
spawn fn -> raise "oops" end
เมื่อรันโปรแกรมข้างต้นจะเกิดข้อผิดพลาดดังต่อไปนี้ -
[error] Process #PID<0.58.00> raised an exception
** (RuntimeError) oops
:erlang.apply/2
บันทึกข้อผิดพลาด แต่กระบวนการวางไข่ยังคงทำงานอยู่ เนื่องจากกระบวนการต่างๆถูกแยกออก หากเราต้องการให้ความล้มเหลวในกระบวนการหนึ่งเผยแพร่ไปยังอีกกระบวนการหนึ่งเราจำเป็นต้องเชื่อมโยงสิ่งเหล่านี้ ซึ่งสามารถทำได้ด้วยไฟล์spawn_linkฟังก์ชัน ให้เราพิจารณาตัวอย่างเพื่อให้เข้าใจตรงกัน -
spawn_link fn -> raise "oops" end
เมื่อรันโปรแกรมข้างต้นจะเกิดข้อผิดพลาดดังต่อไปนี้ -
** (EXIT from #PID<0.41.0>) an exception was raised:
** (RuntimeError) oops
:erlang.apply/2
หากคุณกำลังเรียกใช้สิ่งนี้ใน iexจากนั้นเชลล์จะจัดการกับข้อผิดพลาดนี้และไม่ออก แต่ถ้าคุณเรียกใช้โดยสร้างไฟล์สคริปต์ก่อนแล้วใช้elixir <file-name>.exsกระบวนการพาเรนต์จะถูกปิดลงเนื่องจากความล้มเหลวนี้
กระบวนการและการเชื่อมโยงมีบทบาทสำคัญในการสร้างระบบป้องกันความผิดพลาด ในแอปพลิเคชัน Elixir เรามักจะเชื่อมโยงกระบวนการของเรากับหัวหน้างานซึ่งจะตรวจจับเมื่อกระบวนการตายและเริ่มกระบวนการใหม่แทน สิ่งนี้เป็นไปได้ก็ต่อเมื่อกระบวนการต่างๆถูกแยกออกจากกันและจะไม่แชร์อะไรโดยปริยาย และเนื่องจากกระบวนการต่างๆถูกแยกออกจึงไม่มีทางที่ความล้มเหลวในกระบวนการจะขัดข้องหรือทำให้สถานะอื่นเสียหาย ในขณะที่ภาษาอื่นต้องการให้เราจับ / จัดการข้อยกเว้น ใน Elixir เราสบายดีที่ปล่อยให้กระบวนการล้มเหลวเพราะเราคาดหวังว่าหัวหน้างานจะรีสตาร์ทระบบของเราอย่างถูกต้อง
สถานะ
หากคุณกำลังสร้างแอปพลิเคชันที่ต้องใช้สถานะตัวอย่างเช่นเพื่อเก็บการกำหนดค่าแอปพลิเคชันของคุณไว้หรือคุณต้องการแยกวิเคราะห์ไฟล์และเก็บไว้ในหน่วยความจำคุณจะจัดเก็บไว้ที่ใด ฟังก์ชันกระบวนการของ Elixir จะมีประโยชน์เมื่อทำสิ่งนั้น
เราสามารถเขียนกระบวนการที่วนซ้ำไม่สิ้นสุดรักษาสถานะและส่งและรับข้อความ ตัวอย่างเช่นให้เราเขียนโมดูลที่เริ่มกระบวนการใหม่ที่ทำงานเป็นที่เก็บคีย์ - ค่าในไฟล์ที่ชื่อkv.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
โปรดทราบว่าไฟล์ start_link ฟังก์ชันเริ่มกระบวนการใหม่ที่รันไฟล์ loopฟังก์ชันเริ่มต้นด้วยแผนที่ว่างเปล่า loopจากนั้นจะรอข้อความและดำเนินการตามความเหมาะสมสำหรับแต่ละข้อความ ในกรณีของก:getมันจะส่งข้อความกลับไปยังผู้โทรและโทรวนซ้ำอีกครั้งเพื่อรอข้อความใหม่ ในขณะที่:put ข้อความเรียกใช้จริง loop ด้วยแผนที่เวอร์ชันใหม่พร้อมคีย์และค่าที่กำหนดไว้
ให้เราดำเนินการต่อไปนี้ -
iex kv.exs
ตอนนี้คุณควรอยู่ในไฟล์ iexเปลือก. ในการทดสอบโมดูลของเราให้ลองทำดังต่อไปนี้ -
{: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()
เมื่อรันโปรแกรมข้างต้นจะให้ผลลัพธ์ดังนี้ -
"Hello"