RosettaCodeData/Task/Dining-philosophers/Elixir/dining-philosophers.elixir

122 lines
3.7 KiB
Plaintext

defmodule Philosopher do
defstruct missing: [], clean: [], promised: []
def run_demo do
pid1 = spawn(__MODULE__, :init, ["Russell"])
pid2 = spawn(__MODULE__, :init, ["Marx"])
pid3 = spawn(__MODULE__, :init, ["Spinoza"])
pid4 = spawn(__MODULE__, :init, ["Kant"])
pid5 = spawn(__MODULE__, :init, ["Aristotle"])
# a chopstick is simply represented by the pid of the neighbour that shares it.
send(pid1, {:run, %Philosopher{}})
send(pid2, {:run, %Philosopher{missing: [pid1]}})
send(pid3, {:run, %Philosopher{missing: [pid2]}})
send(pid4, {:run, %Philosopher{missing: [pid3]}})
send(pid5, {:run, %Philosopher{missing: [pid1, pid4]}})
end
def init(philosopher_name) do
receive do
{:run, state} ->
spawn(__MODULE__, :change_state, [self()])
case flip_coin() do
:heads -> thinking(philosopher_name, state)
:tails -> hungry(philosopher_name, state)
end
end
end
defp thinking(philosopher_name, state) do
receive do
{:change_state} ->
hungry(philosopher_name, state)
{:chopstick_request, pid} ->
if clean?(pid, state) do
thinking(philosopher_name, promise_chopstick(philosopher_name, pid, state))
else
give_chopstick(philosopher_name, self(), pid)
%{missing: missing} = state
thinking(philosopher_name, %{state | missing: [pid | missing]})
end
end
end
defp hungry(philosopher_name, state) do
IO.puts "#{philosopher_name} is hungry."
%{missing: missing} = state
for pid <- missing, do: request_chopstick(philosopher_name, self(), pid)
wait_for_chopsticks(philosopher_name, state)
end
defp wait_for_chopsticks(philosopher_name, state) do
if has_chopsticks?(state) do
eating(philosopher_name, state)
end
receive do
{:chopstick_request, pid} ->
if clean?(pid, state) do
wait_for_chopsticks(philosopher_name, promise_chopstick(philosopher_name, pid, state))
else
give_chopstick(philosopher_name, self(), pid)
request_chopstick(philosopher_name, self(), pid)
%{missing: missing} = state
wait_for_chopsticks(philosopher_name, %{state | missing: [pid | missing]})
end
{:chopstick_response, pid} ->
%{missing: missing, clean: clean} = state
wait_for_chopsticks(philosopher_name, %{state | missing: List.delete(missing, pid), clean: [pid | clean]})
end
end
defp eating(philosopher_name, state) do
IO.puts "*** #{philosopher_name} is eating."
receive do
{:change_state} ->
%{promised: promised} = state
for pid <- promised, do: give_chopstick(philosopher_name, self(), pid)
thinking(philosopher_name, %Philosopher{missing: promised})
end
end
defp clean?(pid, state) do
%{clean: clean} = state
Enum.member?(clean, pid)
end
defp has_chopsticks?(state) do
%{missing: missing} = state
Enum.empty?(missing)
end
defp promise_chopstick(philosopher_name, pid, state) do
IO.puts "#{philosopher_name} promises a chopstick."
%{promised: promised} = state
%{state | promised: [pid | promised]}
end
defp request_chopstick(philosopher_name, snd_pid, recv_pid) do
IO.puts "#{philosopher_name} requests a chopstick."
send(recv_pid, {:chopstick_request, snd_pid})
end
defp give_chopstick(philosopher_name, snd_pid, recv_pid) do
IO.puts "#{philosopher_name} gives a chopstick."
send(recv_pid, {:chopstick_response, snd_pid})
end
defp flip_coin do
case Enum.random(0..1) do
0 -> :heads
1 -> :tails
end
end
def change_state(pid) do
Process.sleep(Enum.random(1..10) * 1000)
send(pid, {:change_state})
change_state(pid)
end