1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Erlang:プロセス間の並行プラミング入門1

Last updated at Posted at 2021-11-02

Erlangでプロセス間の並行プログラミングを動かしてみました。
MacBook環境を使いました。
MacBookには、以下の記事でElixirを入れた際に、自動的に入るErlangを使いました。

( 関連記事 )

ErlangとIExインタプリンタ環境のバージョン

  • Eshell : V12.1.3
  • IEx : 1.12.3
Terminal
electron@diynoMacBook-Pro ~ % erl --version
Erlang/OTP 24 [erts-12.1.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit] [dtrace]

Eshell V12.1.3  (abort with ^G)
1> init:stop().
ok
2> %                                                         
electron@diynoMacBook-Pro ~ %
Terminal
electron@diynoMacBook-Pro ~ % iex --version
Erlang/OTP 24 [erts-12.1.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit] [dtrace]

IEx 1.12.3 (compiled with Erlang/OTP 24)
electron@diynoMacBook-Pro ~ %

( 参考 )

  • Fred Hebert(著)・山口能迪(訳)『すごいErlangゆかいに学ぼう!』(Ohmsha)

ターミナルで__erl__と打ち込んで、Erlangの対話型シェルであるIExを立ち上げます。
まずは、整数と実数の数値演算と、値の等価性比較をしてみます。

IEx
electron@diynoMacBook-Pro ~ % erl
Erlang/OTP 24 [erts-12.1.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit] [dtrace]

Eshell V12.1.3  (abort with ^G)
1> 5.0 =:= 5.
* 2:1: syntax error before: 5.0
1> 5.0 =:= 5.0.
true
2> 5.0 =/= 5.0.
false
3> 5.0 =/= 5.  
true
4> 0 == false
4> 0 == false.
* 2:1: syntax error before: 0
4> 0 == false.
false
5> 0 == true. 
false
6> 1 == true.
false
7> 1 < false.
true
8>

次に、パターンマッチです。

IEx
8> Point = {4, 5}.
{4,5}
9> Point.
{4,5}
10> {X, Y} = Point.
{4,5}
11> X.
4
12> Y.
5
13> {X, Y} = {1, 2}.
** exception error: no match of right hand side value {1,2}
14> X.
4
15> X.
4
16> Y.
5
17> Y.
5
18> {X, Y} = {10, 20}.
** exception error: no match of right hand side value {10,20}
  • 2つの変数XとYは、すでに定義済み({X, Y}を{4, 5}とパターンマッチした際に、Xを4に、Yを5に束縛済み)のため、後から、Xを1に、Yを2に上書き再定義__(二重束縛)は__できない
  • 左辺と右辺のパターンマッチは成立しない(左辺と右辺は等価ではない)
  • 変数がどこかで上書き変更されてしまうことが、言語仕様レベルで禁止されている。なので、ある変数を利用するコード(関数)は、常に安心して、その変数を引数として受け入れる(その変数に関数を適用させる)ことができる(参照透過性__と__値不変性__の組み合わせによる__安全性
IEx
18> X.
4
19> Y.
5
20> 
  • 2つの変数XとYは、4と5のまま(値不変性)。
IEx
20> X = 3.
** exception error: no match of right hand side value 3
21> X.
4
22> PreciseTemperature = {Celsius, 21.213}.
* 1:23: variable 'Celsius' is unbound
23> PreciseTemperature = {celsius, 21.213}.
{celsius,21.213}
24> {kelvin, T} = PreciseTemperature.
** exception error: no match of right hand side value {celsius,21.213}
25> {celsius, 5.0} = PreciseTemperature.
** exception error: no match of right hand side value {celsius,21.213}
26> erlang:element(2, {a, b, c}).
b
27> erlang:element(1, {a, b, c}).
a
28> erlang:element(0, {a, b, c}).
** exception error: bad argument
     in function  element/2
        called as element(0,{a,b,c})
        *** argument 1: out of range
29> erlang:element(-10, {a, b, c}).
** exception error: bad argument
     in function  element/2
        called as element(-10,{a,b,c})
        *** argument 1: out of range
30> erlang:element(10, {a, b, c}). 
** exception error: bad argument
     in function  element/2
        called as element(10,{a,b,c})
        *** argument 1: out of range
31>          
32> lists:seq(1,4).
[1,2,3,4]
33> lists:seq(1, 4).
[1,2,3,4]
34> lists:seq(1, -4).
** exception error: no function clause matching lists:seq(1,-4) (lists.erl, line 243)
35> init:stop().
ok
36> %                 
electron@diynoMacBook-Pro ~ %
  • init:stop( ).でIExから抜ける。

スクリプトファイルをコンパイルして動かしてみる

Terminal
electron@diynoMacBook-Pro ~ % mkdir erlang_training
electron@diynoMacBook-Pro ~ % cd erlang_training 
electron@diynoMacBook-Pro erlang_training % vim hello.erl

"hello, world"を出力(副作用)するスクリプトファイル hello.erl

hello.erl
-module(hello).
-export([hello_world/0]).

hello_world() -> io:fwrite("hello, world\n").
  • IExインタプリタ環境で、__c( )__を実行してスクリプトファイルをコンパイル
  • __モジュール名:関数名( )__で、コンパイルした関数を実行
calc.erl
electron@diynoMacBook-Pro erlang_training % erl
Erlang/OTP 24 [erts-12.1.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit] [dtrace]

Eshell V12.1.3  (abort with ^G)
1> c(hello).
{ok,hello}
2> hello:hello_world().
hello, world
ok
3> init:stop().        
ok
4> %                                                                                                         
electron@diynoMacBook-Pro erlang_training %

加算と乗算を定義したスクリプトファイルを用意(calc.erl

calc.erl
-module(calc).
-export([add/2, mul/2]).
add(A,B) -> A+B.
mul(A,B) -> A*B.

  • IEx環境の中でコンパイルして、実行する。
IEx
electron@diynoMacBook-Pro erlang_training % erl
Erlang/OTP 24 [erts-12.1.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit] [dtrace]

Eshell V12.1.3  (abort with ^G)
1> c(calc).
{ok,calc}
2> calc:add(1,2).
3
3> calc:add(1, 2).
3
4>
  • Erlangは、関数の引数の間にスペースを何文字入れてもOK
IEx
4> calc:add(1,  2).
3
5> calc:add(1 ,  2).
3
6> calc:add(  1  ,  2).
3
7> calc:add(  2  ,  7).
9
8> calc:add(2, 7).     
9
9> calc:add(2,7). 
9
10> calc:mul(1, 7).
7
11> calc:mul(3, 7).
21
12> self().
<0.79.0>
13> self().
<0.79.0>
14> self().
<0.79.0>
15> F = fun() -> 2 + 2 end.
# Fun<erl_eval.45.65746770>
16> F
16> F.
* 2:1: syntax error before: F
16> spawn(F).
<0.102.0>
17> spawn(F).
<0.104.0>
18>

プロセス間通信をやってみる

Erlangの強み(使い所)の1つであるプロセス間の並行プログラミング(の触り)を試してみます。

  • spwanを実行するたびに、新たなプロセスが生成される。
  • そのため、標準出力には、その都度、新しいプロセスのプロセスID(PID)が表示される。

ここからは、以下の教科書所収のコードを写経して叩いていきます。

  • Fred Hebert(著)・山口能迪(訳)『すごいErlangゆかいに学ぼう!』(Ohmsha)
IEx
18> spawn(F).
<0.106.0>
19> spawn(F).
<0.108.0>
20> spawn(F).
<0.110.0>
21> spawn(F).
<0.112.0>
29> H = fun(X) -> timer:sleep(10), io:format("~p~n", [X]) end.
# Fun<erl_eval.44.65746770>
30> [spawn(fun() -> H(X) end) || X <- lists:seq(1,10)].       
[<0.155.0>,<0.156.0>,<0.157.0>,<0.158.0>,<0.159.0>,
 <0.160.0>,<0.161.0>,<0.162.0>,<0.163.0>,<0.164.0>]
1   
2   
3   
4   
5   
6   
7   
8   
9   
10  
31> [spawn(fun() -> H(X) end) || X <- lists:seq(1,10)].
[<0.166.0>,<0.167.0>,<0.168.0>,<0.169.0>,<0.170.0>,
 <0.171.0>,<0.172.0>,<0.173.0>,<0.174.0>,<0.175.0>]
1   
2   
3   
4   
5   
6   
7   
8   
9   
10  
32> [spawn(fun() -> H(X) end) || X <- lists:seq(1,10)].
[<0.177.0>,<0.178.0>,<0.179.0>,<0.180.0>,<0.181.0>,
 <0.182.0>,<0.183.0>,<0.184.0>,<0.185.0>,<0.186.0>]
1   
2   
3   
4   
5   
6   
7   
8   
9   
10  
33> [spawn(fun() -> H(X) end) || X <- lists:seq(1,10)].
[<0.188.0>,<0.189.0>,<0.190.0>,<0.191.0>,<0.192.0>,
 <0.193.0>,<0.194.0>,<0.195.0>,<0.196.0>,<0.197.0>]
1   
2   
3   
4   
5   
6   
7   
8   
9   
10  
44> [spawn(fun() -> H(X) end) || X <- lists:seq(1,10)].       
[<0.219.0>,<0.220.0>,<0.221.0>,<0.222.0>,<0.223.0>,
 <0.224.0>,<0.225.0>,<0.226.0>,<0.227.0>,<0.228.0>]
1   
2   
3   
4   
5   
6   
7   
8   
9   
10  
45> self() ! hello.
hello
46> self() ! ciao. 
ciao
47> self() ! self() ! double
47> self() ! self() ! double.
* 2:1: syntax error before: self
47> self() ! self() ! double.
double
48> flush().
Shell got hello
Shell got ciao
Shell got double
Shell got double
ok
49> self() ! self() ! self() ! triple. 
triple
50> flush().                          
Shell got triple
Shell got triple
Shell got triple
ok
51> self() ! self() ! self() ! triple2.
triple2
52> flush().                           
Shell got triple2
Shell got triple2
Shell got triple2
ok
53> self() ! self() ! self() ! triple3.
triple3
54> self() ! self() ! double2.         
double2
55> self() ! self() ! self() ! triple4.
triple4
56> flush().                           
Shell got triple3
Shell got triple3
Shell got triple3
Shell got double2
Shell got double2
Shell got triple4
Shell got triple4
Shell got triple4
ok
57> init:stop().                       
ok
58> %
electron@diynoMacBook-Pro erlang_training %
  • receiveで受け取った値の中身に応じて、異なる返り値を返す関数dolphin1を定義する。
  • 各引数に対応する返り値を定義。
dolphins.erl
-module(dolphins).
-compile(export_all).

dolphin1() ->
   receive
       do_a_flip ->
           io:format("How about no?~n");
       fish ->
           io:format("So long and thanks for all the fish!~n");
       _ ->
           io:format("Heh, we're smarter than you humans.~n")
       end.

IEx
electron@diynoMacBook-Pro erlang_training % erl
Erlang/OTP 24 [erts-12.1.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit] [dtrace]

Eshell V12.1.3  (abort with ^G)
1> c(dolphins).
dolphins.erl:2:2: Warning: export_all flag enabled - all functions will be exported
%    2| -compile(export_all).
%     |  ^

{ok,dolphins}
2> 
2> Dolphin = spawn(dolphins, dolphin1, []).
<0.86.0>
3> Dolphin ! "oh, hello dolphins!".
Heh, we're smarter than you humans.
"oh, hello dolphins!"
4> Dolphin ! "Bon jour, Monsieur!".
"Bon jour, Monsieur!"
  • 4>では、返り値として、__"oh, hello dolphins!"が__返されなかった。
  • 3>で、Dolphinプロセスは仕事を終えて、死んでしまっているため。
  • 新たに、spawn関数で新しいプロセスを立ち上げ直す必要がある。
IEx
5> Dolphin = spawn(dolphins, dolphin1, []).
** exception error: no match of right hand side value <0.90.0>
  • Dolphin変数はすでに定義済み(spwan(dolphins, dolphin1, [])に束縛済みなので、二重束縛はできない。
  • 左辺と右辺のパターンマッチは成立しない(左辺と右辺は等価ではない)
IEx
6> f(Dolphin).
ok
7> Dolphin = spawn(dolphins, dolphin1, []).
<0.95.0>
8> Dolphin ! "Bon jour, Monsieur!".        
Heh, we're smarter than you humans.
"Bon jour, Monsieur!"
9> f(Dolphin).                             
ok
10> Dolphin = spawn(dolphins, dolphin1, []).
<0.99.0>
11> Dolphin ! "fish".                       
Heh, we're smarter than you humans.
"fish"
12> f(Dolphin).                             
ok
13> Dolphin = spawn(dolphins, dolphin1, []).
<0.103.0>
14> Dolphin ! fish.                         
So long and thanks for all the fish!
fish
15> f(Dolphin).                             
ok
16> Dolphin = spawn(dolphins, dolphin1, []).
<0.107.0>
17> Dolphin ! do_a_flip.                    
How about no?
do_a_flip
18> init:stop().                            
ok
19> %                                                                                                        electron@diynoMacBook-Pro erlang_training %

教科書に掲載されている関数 delphin2を、dolphinsモジュールに追記します。

  • dolphin2関数では、返り値を標準出力に表示(io:format)するのではなく、引数として受けとったPIDの持ち主であるプロセスに宛てて、返り値を送信(プロセス間通信)します。
  • 但し、引数がdo_a_flipfish以外の任意の変数を受け取った場合は、これまで通り、標準出力にメッセージを表示します。
  • dolphin2関数は、fishを受け取った場合を除いて、処理を実行後に、自分自身を再帰的に繰り返し実行します。その結果、処理を実行した後もこのモジュールを呼び出したプロセスは死なないで、新しい引数を待ち続ける状態で生き続けます。
dolhins.erl
-module(dolphins).
-compile(export_all).

dolphin1() ->
   receive
       do_a_flip ->
           io:format("How about no?~n");
       fish ->
           io:format("So long and thanks for all the fish!~n");
       _ ->
           io:format("Heh, we're smarter than you humans.~n")
       end.

dolphin2() ->
   receive
       {From, do_a_flip} ->
           From ! "How about no?";
       {From, fish} ->
           From ! "So long and thanks for all the fish!";
       _ ->
           io:format("Heh, we're smarter than you humans.~n")
   end.

dolphin3() ->
   receive
       {From, do_a_flip} ->
           From ! "How about no?",
           dolphin3();
       {From, fish} ->
           From ! "So long and thanks for all the fish!";
       _ ->
           io:format("Heh, we're smarter than you humans.~n"),
           dolphin3()
   end.

IEx
electron@diynoMacBook-Pro erlang_training % erl
Erlang/OTP 24 [erts-12.1.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit] [dtrace]

Eshell V12.1.3  (abort with ^G)
1> 
2> c(dolphins).
dolphins.erl:2:2: Warning: export_all flag enabled - all functions will be exported
%    2| -compile(export_all).
%     |  ^
{ok,dolphins}
3> Dolphin2 = spawn(dolphins, dolphin2, []).
<0.86.0>
4> Dolphin2 ! {self(), do_a_flip}.  
{<0.88.0>,do_a_flip}
5> flush().
Shell got "How about no?"
ok
6> Dolphin2 ! {self(), do_a_flip}.
{<0.88.0>,do_a_flip}
7> flush().                       
ok
8> Dolphin2 ! {self(), hi}.       
{<0.88.0>,hi}
9> flush().                
ok
10> f(Dolphin2).                             
ok
11> Dolphin2 = spawn(dolphins, dolphin2, []).
<0.97.0>
12> Dolphin2 ! {self(), hi}.                 
Heh, we're smarter than you humans.
{<0.88.0>,hi}
13> flush().                                 
ok
14> f(Dolphin2).                             
ok
15> Dolphin2 = spawn(dolphins, dolphin2, []).
<0.102.0>
16> Dolphin2 ! {self(), fish}.               
{<0.88.0>,fish}
17> flush().                                 
Shell got "So long and thanks for all the fish!"
ok
18> f(Dolphin2).                             
ok
19> 
IEx
36> Dolphin3 = spawn(dolphins, dolphin3, []).
<0.125.0>
37> Dolphin3 ! {self(), hi}.                 
Heh, we're smarter than you humans.
{<0.88.0>,hi}
38> Dolphin3 ! {self(), do_a_flip}.
{<0.88.0>,do_a_flip}
39> Dolphin3 ! {self(), do_a_flip}.
{<0.88.0>,do_a_flip}
40> Dolphin3 ! {self(), do_a_flip}.
{<0.88.0>,do_a_flip}
41> flush().                                 
Shell got "How about no?"
Shell got "How about no?"
Shell got "How about no?"
ok
42> Dolphin3 ! {self(), do_a_flip}.
{<0.88.0>,do_a_flip}
43> Dolphin3 ! {self(), hello}.    
Heh, we're smarter than you humans.
{<0.88.0>,hello}
44> Dolphin3 ! {self(), hello}.
Heh, we're smarter than you humans.
{<0.88.0>,hello}
45> Dolphin3 ! {self(), fish}. 
{<0.88.0>,fish}
46> Dolphin3 ! {self(), do_a_flip}.
{<0.88.0>,do_a_flip}
47> flush().                       
Shell got "How about no?"
Shell got "So long and thanks for all the fish!"
ok
48> f(Dolphin3).
ok
49> init:stop().
ok
50> %                                                                                                        electron@diynoMacBook-Pro erlang_training % 
1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?