K.Sasada's Home Page

こめんとのついか

こめんとこめんと!

message

please add long comment :).

_24(Sat)

Pony で Actor の GC がどうのってのがあって、ぴんとこなかったんだけど、やっとわかった。

Elixir(多分 Erlang も)の場合、こんな感じで、誰からも参照されない Process を沢山作って、永遠に待つような例が書ける。誰も参照していないので、その Process にメッセージが届くことはない。

defmodule Test do
  def waiter n do
    IO.puts(n)
    receive do
      # infinite loop
    end
  end

  def make 0 do
  
  end
  def make n do
    spawn(Test, :waiter, [n])
    make(n-1)
  end
end

Test.make(100_000)
receive do
  # wait ...
end

この場合、何が起こるかと言うと、生成した実行中のプロセスが永遠に待つことになる。なるほどなぁ。

Pony の場合、receive 相当が言語組込みなので、この問題は起きず、誰も参照していない、待ちの Actor はガシガシ消せる、ということなのか。


昔、八杉先生から become があるから Actor なんだよ、と教えて貰って、なんとなく想像していたことが、原論文を読むとさっくりわかって、やっぱりちゃんと読まないとなぁ、などと。

Erlang は、なぜああいう receive を陽に書くデザインにしたんだろうな。 というか、最近 Actor って言ってるやつって、みんな receive してない?

Akka の場合は、receive 関数を定義するから、それが become 相当ってことか。

sinatra の do_GET みたいなものかな。

Guild で Actor を標準で備えるぜー、と思ってたけど、どうやらそれじゃ駄目だな。


package main

import "fmt"
import "time"

func waiter(n int, ch chan int) {
  // fmt.Println(n)
  var i = <- ch  // wait forever
  fmt.Println(i) // unreachable
}

func invoker(max int) {
  var i = 0
  for ; i<max; i++ {
    var ch = make(chan int)
    go waiter(i, ch)
    // forget ch, so that nobody can send message via this channel.
  }
}

func main() {
  invoker(100000)
  time.Sleep(10 * time.Second)
}

golang を久々に書いた。何もわからないので、まずはインストールの方法をぐぐるところからだった...。

で、結論からいうと、多分やはり goroutine は残る。

最初、main goroutine を、<- ch で待とうとすると、すべての goroutine が待ちに入るからだと思うから、デッドロックだと検出されたので、お、やるな、と思ったんだけど、別に GC されるというわけでもないようだ。

って、待てよ、GC が起きていないだけ、という気もするな ... と思ったけど、100000 個作ったら、さすがに何個かは消えるよな。一応、ゴミを作って確かめよう。

package main

import "fmt"
import "time"

func waiter(n int, ch chan int) {
  // fmt.Println(n)
  var i = <- ch  // wait forever
  fmt.Println(i) // unreachable
}

func invoker(max int) {
  var i = 0
  for ; i<max; i++ {
    var ch = make(chan int)
    go waiter(i, ch)
    // forget ch, so that nobody can send message via this channel.
  }
}

func main() {
  invoker(100000)
  var i = 0
  var s = "foo"
  for ; i<28; i++ {
    s = s + s // 2^28
  }
  time.Sleep(10 * time.Second)
}

メモリ消費量を見ると、消えてないっぽい。main が終わるとプロセスは終了する。


しかし、time.Sleep(3 * time.Second) って冗長だけどわかりやすいな。time.Sleep(3.second) って Ruby だと書かせたくなるのかな。Elixir だと、そこはマクロにするんだろうな。駄目だ、複雑だ。


さて、Guild はどうするべきか。つまり、状態は 4 つある。

今の Thread は、走っていれば GC されない(root になる)ので、(4) 以外は GC されない。

(2) は I/O 待ちか何か、やるべき仕事で停まっている。(3) はやるべき仕事を待っているが、誰もそこに仕事を投げないので GC されうる。

(3) で停まっているとき、つまり channel 待ちの時、その channel への参照が GC されたら(つまり、自 Guild 以外、その channel を持たないのであれば)、その channel で待つと例外、とすればいいのかな。

自 Guild で channel 経由した通信はできなくなるけど(ch <- 1; var v = <- ch みたいな)、まぁ、そこは無理してサポートしなくても良いよね。Thread 使うなら、Queue を使ってくれ。


しかし、dynamic reconfiguration のための become ってのが、イマイチピンと来ない。ネットワークレイアウトが決まっているから、ということだが、物理的なそこと、どうつながるのか。


Erlang は Actor model を指向したものではなく、少なくとも開発時には知らなかった、ということを教えてもらった

actor model の要件がイマイチわからないし、理論的には同一のものだよね、と言えるのかもしれない。が、イマイチ自信がない。無限に待つような receive を書くことが出来るが(そして、それで無限に待てるけど)、それは actor model では許容されてるんだろうか。デッドロックだよなぁ。

上記で考えたとおり、回避することは出来るので、単に実装の問題、と言えるような気もするけれど。

_yugui(Sun Oct 09 16:23:37 +0900 2016)

Golangのそれはgoroutine-leakと呼ばれていて、回避するたのめtipsは若干あるものの「そういうもの」と考えられています。


好きなだけ長いコメントをどうぞ。

お名前


back

tton 記述が使えます。YukiWikiな記述してりゃ問題ありません。

「行頭に#code」 と、「行頭に#end」 で挟むと、その間の行は pre で囲まれます。プログラムのソースを書くときに使ってください。

例:

#code

(なんかプログラム書く)

#end

リンクは

[[なまえ|http://www.example.org]]

とか

[[http://www.example.org]]

で貼れます。

$Date: 2003/04/28 10:27:51 $