質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Erlang

Erlangとは、多目的でありガーベッジコレクションを行うプログラミング言語および実行環境です。Erlangは並行処理・分散化された環境・フォルトトレランスを実装しています。

Q&A

解決済

1回答

430閲覧

Erlang : register/0がうまく動かない

MaSaKai

総合スコア11

Erlang

Erlangとは、多目的でありガーベッジコレクションを行うプログラミング言語および実行環境です。Erlangは並行処理・分散化された環境・フォルトトレランスを実装しています。

0グッド

0クリップ

投稿2020/03/02 13:05

前提

検索してみましたが、思っていた解答がなかったのでここに質問させていただきます。
Erlangの勉強を始めたばかりの初心者なので、
ソースコードに突っ込みどころがあるとは思いますがお許しください。

実現したいこと

図1の該当のソースコードのp_sleeping()p_wake_up()のそれぞれをsleepwakeで登録したい。

発生している問題

Erlang

1start(Name, Fun) -> 2 register(Name, spawn(Fun)), 3 io:format("Finish_register~n").

上記のコードのregister/2が思った通りに動かない。
エラーは吐かないがwhereis/0undefinedが返ってくる。

該当のソースコード

Erlang

1-module(exercises_v1). 2-author("MaSaKai"). 3 4%%-compile(export_all). 5-export([rpc/1, generate_sleeping_process/0, generate_wake_process/0]). 6 7rpc(Pid) -> 8 Pid ! {self(), start_sleep}, 9 Pid ! {self(), start_wake_up}, 10 receive 11 {Pid, Fun} -> 12 Fun() 13 after 5000 -> 14 io:format("Time out!~n") 15 end. 16 17p_sleeping() -> 18 receive 19 {Pid_rpc, start_sleep} -> 20 start(sleeping, S_Fun = fun() -> io:format("SLEEP!~n") end), 21 Pid_rpc ! {self(), S_Fun} 22 end. 23 24p_wake_up() -> 25 receive 26 {Pid_rpc, start_wake_up} -> 27 start(waking_up, W_Fun = fun() -> io:format("Wake Up!~n") end), 28 Pid_rpc ! {self(), W_Fun} 29 end. 30 31generate_sleeping_process() -> 32 spawn(fun p_sleeping/0). 33 34generate_wake_process() -> 35 spawn(fun p_wake_up/0). 36 37start(Name, Fun) -> 38 register(Name, spawn(Fun)), 39 io:format("Finish_register~n").

図1 : 該当のソースコード(Erlang)

補足情報

Verion : Erlang (SMP,ASYNC_THREADS) (BEAM) emulator version 10.6
Editor : Intellij

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

上記のコードのregister/2が思った通りに動かない。
エラーは吐かないがwhereis/0undefinedが返ってくる。

undefinedが返ってくる理由は、register/2で登録したプロセスがwhereis/0を呼んだときにすでに終了しているからです。

Erlangのプロセスはspawn/2で起動し、引数として渡された関数を実行します。そして関数から戻るとプロセスも終了します。ご質問のコードではspawn/2fun() -> io:format("SLEEP!~n") endという関数を渡しています。この関数はターミナルSLEEP!と表示したらすぐに戻りますので、せっかく起動したプロセスもすぐに終了してしまいます。

同じような関数で少し実験してみましょう。以下のようなプログラムを書いてみます。

examples.erl

erlang

1-module(examples). 2-export([hello/0]). 3 4hello() -> 5 io:format("hello[~p]: Hello!~n", [self()]).

examplesモジュールのhello関数はターミナルに自分のPIDとHello!という文字列を表示してすぐに戻ります。これをspawn/3で実行してみましょう。

erl

1$ erl 2 3> c("examples"). 4{ok,examples} 5 6> Pid1 = spawn(examples, hello, []). 7hello[<0.91.0>]: Hello! 8<0.91.0> 9 10%% erlang:is_process_alive/1は与えられたPIDを持つプロセスが 11%% 存在するならtrueを返す 12> is_process_alive(Pid1). 13false

このようにPIDが<0.91.0>のプロセスが起動したものの、is_process_alive/1を実行したときにはすでに終了してしまっていることがわかります。

うまく動かすには関数がすぐに終了しないように関数の実行を途中で止めなければいけません。例えば以下のechoのようにreceiveを呼ぶと、何かメッセージを受信するまでその場で待たされますので、とりあえずはうまくいきます。

erlang

1-export([hello/0, echo/0, rpc/2]). 2 3%% この関数は受信したメッセージを送信者に送り返す。メッセージを1つ受信したら関数から戻る 4echo() -> 5 receive 6 %% 送信者へメッセージを送り返す 7 {Sender, Msg} -> 8 io:format("echo[~p]: Received ~p from ~p~n", [self(), Msg, Sender]), 9 Sender ! {self(), Msg} 10 end. 11 12rpc(Pid, Msg) -> 13 Pid ! {self(), Msg}, 14 io:format("rpc[~p]: Sent ~p to ~p~n", [self(), Msg, Pid]), 15 receive 16 {Pid, Msg1} -> 17 io:format("rpc[~p]: Received ~p from ~p~n", [self(), Msg1, Pid]) 18 after 5000 -> 19 io:format("Time out!~n") 20 end.

実行してみます。

erl

1> Pid2 = spawn(examples, echo, []). 2<0.94.0> 3 4> is_process_alive(Pid2). 5true 6 7%% メッセージを送信する 8> examples:rpc(Pid2, "Hey!"). 9rpc[<0.84.0>]: Sent "Hey!" to <0.94.0> 10echo[<0.94.0>]: Received "Hey!" from <0.84.0> 11rpc[<0.84.0>]: Received "Hey!" from <0.94.0> 12ok 13 14%% receiveを実行したあとに関数から戻るのでプロセスが終了する 15> is_process_alive(Pid2). 16false

このように1回だけメッセージが受信できるようになりました。

もしプロセスにいくつもメッセージを受信させたいなら、関数から戻らないように関数内で自分自身を呼び出す必要があります。

erlang

1-export([hello/0, echo/0, echo_loop/0, rpc/2]). 2 3%% この関数は受信したメッセージを送信者に送り返す 4%% quitというメッセージを受信しない限りは、自分自身を呼び出すことで同じ処理を繰り返す 5echo_loop() -> 6 receive 7 %% quitを受信したら終了する 8 {Sender, quit} -> 9 io:format("echo_loop[~p]: Received quit from ~p~n", [self(), Sender]), 10 Sender ! {self(), "Bye."}; 11 %% それ以外のメッセージは送信者へ送り返す 12 {Sender, Msg} -> 13 io:format("echo_loop[~p]: Received ~p from ~p~n", [self(), Msg, Sender]), 14 Sender ! {self(), Msg}, 15 %% 終了しないように自分自身を呼び出す 16 echo_loop() 17 end.

erl

1> Pid3 = spawn(examples, echo_loop, []). 2<0.104.0> 3 4> examples:rpc(Pid3, "Howdy!"). 5rpc[<0.102.0>]: Sent "Howdy!" to <0.104.0> 6echo_loop[<0.104.0>]: Received "Howdy!" from <0.102.0> 7rpc[<0.102.0>]: Received "Howdy!" from <0.104.0> 8ok 9 10%% まだプロセスが動いている 11> is_process_alive(Pid3). 12true 13 14> examples:rpc(Pid3, "Yo!"). 15rpc[<0.102.0>]: Sent "Yo!" to <0.104.0> 16echo_loop[<0.104.0>]: Received "Yo!" from <0.102.0> 17rpc[<0.102.0>]: Received "Yo!" from <0.104.0> 18ok 19 20%% quitメッセージを送るとecho_loop/0関数から戻るのでプロセスが終了する 21> examples:rpc(Pid3, quit). 22rpc[<0.102.0>]: Sent quit to <0.104.0> 23echo_loop[<0.104.0>]: Received quit from <0.102.0> 24rpc[<0.102.0>]: Received "Bye." from <0.104.0> 25ok 26 27> is_process_alive(Pid3). 28false

ご質問のプログラムに戻ります。

図1の該当のソースコードのp_sleeping()p_wake_up()のそれぞれをsleepwakeで登録したい。

p_sleeping/0関数の中でstart(sleeping, S_Fun = fun() -> io:format("SLEEP!~n") end)のようにするのではなく、generate_sleeping_process/0関数でstart(sleep, fun p_sleeping/0)のようにすれば登録できます。

erlang

1rpc(Pid) -> 2 %% 内容は変更なし 3 4p_sleeping() -> 5 receive 6 {Pid_rpc, start_sleep} -> 7 %% ここでstart/2を呼ぶのはやめる 8 S_Fun = fun() -> io:format("SLEEP!~n") end, 9 Pid_rpc ! {self(), S_Fun} 10 end. 11 12p_wake_up() -> 13 receive 14 {Pid_rpc, start_wake_up} -> 15 %% ここでstart/2を呼ぶのはやめる 16 W_Fun = fun() -> io:format("Wake Up!~n") end, 17 Pid_rpc ! {self(), W_Fun} 18 end. 19 20generate_sleeping_process() -> 21 %% start/2を呼ぶように変更する 22 start(sleep, fun p_sleeping/0). 23 24generate_wake_process() -> 25 %% start/2を呼ぶように変更する 26 start(wake, fun p_wake_up/0). 27 28start(Name, Fun) -> 29 register(Name, spawn(Fun)), 30 io:format("Finish_register~n").

実行結果

> c("exercises_v1"). {ok,exercises_v1} > exercises_v1:generate_sleeping_process(). Finish_register ok > exercises_v1:generate_wake_process(). Finish_register ok > PidS = whereis(sleep). <0.95.0> > PidW = whereis(wake). <0.97.0> > exercises_v1:rpc(PidS). SLEEP! ok > exercises_v1:rpc(PidW). Wake Up! ok %% p_sleeping/0関数は1回receiveすると戻るので、2回目の呼び出しはできない > exercises_v1a:rpc(PidS). Time out! ok

ただし、上の最後のrpc(PidS)呼び出しがtime outになることからわかるとおり、p_sleeping/0関数とp_wake_up/0関数はメッセージを1回ずつしか受信できません。メッセージを何度も受信させたい場合はecho_loop/0関数のように自分自身を呼び出す必要があります。

投稿2020/03/03 02:32

tatsuya6502

総合スコア2035

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

MaSaKai

2020/03/04 02:07

回答ありがとうございます。 自分のプロセスに対する考え方が誤っていたことが十分にわかりました。 詳細に説明していただきわかりやすかったです。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問