2013/11/12


golang の特徴と言えば goroutine と channel ですが、その使いどころに悩む人もおられる様です。
goroutine は非同期に実行される処理、channel はその groutine と通信する為の仕組みと考えると分かりやすいです。
package main

import (
    "fmt"
    "time"
)

func main() {
    task := make(chan string)
    taskquit := make(chan bool)
    workerquit := make(chan bool)

    go func() {
    loop:
        for {
            select {
            case <-taskquit:
                workerquit <- true
                break loop
            case job := <-task:
                fmt.Println(job)
            }
        }
    }()

    go func() {
        for n := 0; n < 3; n++ {
            task <- fmt.Sprintf("お仕事%03d", n+1)
            time.Sleep(1 * time.Second)
        }
        taskquit <- true
    }()

    <-workerquit
}
この様に goroutine との間でキューの役割を果たします。channel はバッファを持っておりある程度の量ならばブロックする事無く goroutine と通信出来ます(バッファサイズはmakeで変更可能)。
この goroutine と channel をどの様に使うかですが、まず非同期を意識せずに主たる目的を持って処理を書きます。
  • ある処理から「お仕事」が3回投げかけられる
  • ある処理では「お仕事」の依頼を受けると逐次処理する
ここを非同期にする事でパフォーマンスアップを行います。速度面もそうですが上記の様な処理を順番に行う為には全ての「お仕事」をメモリに蓄積しておくか、「お仕事」の依頼があるたびにその処理を実行するコールバックを呼び出してやる必要があります。
しかし groutine と channel を使う事で
  • お仕事の依頼を投げる側
  • お仕事の依頼を受ける側
に処理を分割する事が出来ます。お仕事を投げる側はソケットか何かで電文を受信してパースし、チャネルに「お仕事」を投げつけます。
task <- fmt.Sprintf("お仕事%03d", n+1)
そしてお仕事を受ける側はその channel を指定して「お仕事」を受け取ります。
しかしお仕事を受ける側は、永遠にループする訳にはいけません。終了指示があれば中途半端にお仕事を処理せずに、グレースフルに終了したい物です。そこで終了用の channel を用意して、それを両方 select します。
例ではタスク登録 goroutine 終了用とワーカー終了用を用意しました。
select {
case <-taskquit:
    workerquit <- true
    break loop
case job := <-task:
    fmt.Println(job)
}
元々、別の関数で実装されていた処理というのは並行でない事はもちろんですが、多くのメモリを必要とします。さらにそれをコールバックベースの処理に作り替えると、大きくコードを壊さなければなりません。また純粋に並行ではありません。
golang の goroutine と channel を使えば、既存の処理を簡単に並行化する事が出来るのです。
今回の例では両方共に goroutine にしましたが、この目的で言えばお仕事を投げる側は goroutine でなくても構いません。
例えば Web でデータを登録し、それをバックグランドジョブとして後処理したいといった場合に、この channel の威力が発揮されるでしょう。

追記
先にワーカーが終了しないケースがあったのでコードを修正。
Posted at by



2013/09/19


golang - Go言語における埋め込みによるインタフェースの部分実装パターン - Qiita [キータ]
http://qiita.com/tenntenn/items/e04441a40aeb9c31dbaf
golang はインタフェースがマッチしているかどうかにより処理を切り分けられる。 package main

import "fmt"

type Person struct {
    FirstName string
    LastName  string
}

func (p *Person) Name() string {
    return p.FirstName + " " + p.LastName
}

func main() {
    person := &Person{"Taro""Yamada"}
    fmt.Println(person.Name())
}
メソッドを保持しているのであれば、インタフェースにアサーション出来る。
package main

import "fmt"

type Person struct {
    FirstName string
    LastName  string
}

func (p *Person) Name() string {
    return p.FirstName + " " + p.LastName
}

type Named interface {
    Name() string
}

func printName(named Named) {
    fmt.Println(named.Name())
}

func main() {
    person := &Person{"Tarou""Yamada"}
    printName(person)
}
そして実は型がマッチしているかを確認する事も出来る。interface{} にキャストした後に指定のインタフェースで型アサーションを行う。その際に戻り値の2つめにアサーションが成功したかどうかが返される。 package main

import "fmt"

type Person struct {
    FirstName string
    LastName  string
}

func (p *Person) Name() string {
    return p.FirstName + " " + p.LastName
}

type Named interface {
    Name() string
}

// パーさんは Name メソッドを持たない
type Persan struct {
    FirstName string
    LastName  string
}

func printName(named Named) {
    fmt.Println(named.Name())
}

func main() {
    person := &Person{"Tarou""Yamada"}
    named, ok := interface{}(person).(Named)
    if ok {
        printName(named)
    } else {
        fmt.Println("Person is not Named intreface")
    }

    persan := &Persan{"Tarou""Yamada"}
    named, ok = interface{}(persan).(Named)
    if ok {
        printName(named)
    } else {
        fmt.Println("Persan is not Named intreface")
    }
}
つまり例えば、ライブラリやドライバが新しいメソッドに対応しているかどうか、と言った事を動的に確認出来る様になります。
この手法はオフィシャルパッケージの database/sql でも使われており、各ドライバが指定のインタフェースを実装しているかどうかを確認しています。 type Execer interface {
    Exec(query string, args []Value) (Result, error)
}
Exec というメソッドを持ったインタフェース Execer を用意して if execer, ok := dc.ci.(driver.Execer); ok {
}
Execer にアサーション可能な場合だけ特別な処理を行う、という事をやっています。
C/C++ の様に依存ライブラリの特定バージョン以降でメソッドがあったり無かったりという呪縛から解き放たれる訳です。
これがリフレクション無しに出来るのは便利ですね。
Posted at by



2013/09/18


golang - Go言語でWebAppの開発に必要なN個のこと - Qiita [キータ]

http://golang.org/cmd/go/#hdr-Download_and_install_packages_and_dependencies アプリケーションサーバ 標準パッケージの ne...

http://qiita.com/tenntenn/items/b8b27e32c28f7569f41a
tenntenn さんに開発版を書いて貰ったので運用編を書こうかと思った。
なお、以下のプロダクトは全て golang で書かれているが、ruby や perl のアプリケーションを運用する際にも使える。

リバースプロキシ

まずはリバースプロキシ。フロントは nginx にしてパスによりアプリを切り分ける。(別に直でもいいけど)
mattn/gorem - GitHub
https://github.com/mattn/gorem
ゴーレムと読みます。インストールは以下の様にして行う。 $ go get github.com/mattn/gorem
設定ファイルは以下の様に config.json を用意する。 {
  "mattn": {
    "address""127.0.0.1:11006",
    "entries": [
      { "path""/app1/""backend""http://localhost:10086" },
      { "path""/app2/""backend""http://localhost:10087" },
      { "path""/",      "backend""/home/mattn/tmp/blog/_site" }
    ]
  }
}
127.0.0.1:11006 がサーバのポート。これにより /app1/ へのリクエストが http://localhost:10086//app2/ へのリクエストが http://localhost:10087/ へ転送される。 / へのリクエストは、生成した静的コンテンツ(ここでは jekyll で生成したファイル)を指す。
なお、gorem は /app1/foo というリクエストを http://localhost:10086/app1/foo ではなく http://localhost:10086/foo に転送するので、もし http://localhost:10086/app1/foo に転送したい場合は {
  "mattn": {
    "address""127.0.0.1:11006",
    "entries": [
      { "path""/app1/""backend""http://localhost:10086""use_path"true },
      { "path""/app2/""backend""http://localhost:10087" },
      { "path""/",      "backend""/home/mattn/tmp/blog/_site" }
    ]
  }
}
の様に use_path を指定する。また CGI が使いたい場合は {
  "mattn": {
    "address""127.0.0.1:11006",
    "entries": [
      { "path""/app1/""backend""http://localhost:10086""use_path"true },
      { "path""/app2/""backend""http://localhost:10087/foo.cgi""cgi"true },
      { "path""/",      "backend""/home/mattn/tmp/blog/_site" }
    ]
  }
}
の様に cgi 設定する。設定は複数記述する事ができ {
  "mattn": {
    "address""127.0.0.1:11006",
    "entries": [
      { "path""/app1/""backend""http://localhost:10086""use_path"true },
      { "path""/app2/""backend""http://localhost:10087/foo.cgi""cgi"true },
      { "path""/",      "backend""/home/mattn/tmp/blog/_site" }
    ]
  },
  "thinca": {
    "address""127.0.0.1:11008",
    "entries": [
      { "path""/app1/""backend""http://localhost:10088""use_path"true },
      { "path""/app2/""backend""http://localhost:10089/foo.cgi""cgi"true },
      { "path""/",      "backend""/home/thinca/tmp/blog/_site" }
    ]
  }
}
の様にも書ける。動作させたまま設定を読み込みなおす場合は、gorem のプロセスに SIGHUP を送る。 $ kill -HUP 12345 上記の設定で thinca だけ再読み込みしたい場合は {
  "mattn": {
    "address""127.0.0.1:11006",
    "entries": [
      { "path""/app1/""backend""http://localhost:10086""use_path"true },
      { "path""/app2/""backend""http://localhost:10087/foo.cgi""cgi"true },
      { "path""/",      "backend""/home/mattn/tmp/blog/_site" }
    ]
    "flagfile""/tmp/gorem.mattn"
  },
  "thinca": {
    "address""127.0.0.1:11008",
    "entries": [
      { "path""/app1/""backend""http://localhost:10088""use_path"true },
      { "path""/app2/""backend""http://localhost:10089/foo.cgi""cgi"true },
      { "path""/",      "backend""/home/thinca/tmp/blog/_site" }
    ],
    "flagfile""/tmp/gorem.thinca"
  }
}
の様に設定しておき $ touch /tmp/gorem.thinca とすれば thinca だけ再読込される。

プロセスマネージャ

ruby に foreman というツールがある。Procfile で指定したプロセス情報と .env という環境変数情報を参照してプロセスを起動する。 しかし軽量ではない。そこで私は goreman というツールを使っている。
mattn/goreman - GitHub
https://github.com/mattn/goreman
インストールは以下の様にして行う。 $ go get github.com/mattn/goreman Procfile の書き方は foreman と同じ。 gorem: /home/mattn/dev/gorem/gorem -c /home/mattn/dev/gorem/config.json
my_app1: ruby /home/mattn/dev/my_app1/web.rb
my_app2: ruby /home/mattn/dev/my_app2/web.rb
ここで先ほどの gorem も一緒に起動しておく。例えば上記の gorem の設定だと /app1 を my_app1 に、/app2 を my_app2 に振り分ける。 起動は Procfile のあるディレクトリで $ goreman start とすればいい。単独で起動停止したい場合は別の端末で $ goreman run stop my_app2
$ goreman run start my_app2
$ goreman run restart my_app2
とする。内部で RPC を使っているがポートを変えたい場合は環境変数 GOREMAN_RPC_PORT で変更する。
memcached 等も一緒に起動しておくと良い。

Github インテグレーション

github に push したら勝手にデプロイして欲しい。
mattn/gost - GitHub
https://github.com/mattn/gost
インストールは以下の様にして行う。 $ go get github.com/mattn/gost 起動は goreman の Procfile に含めて起動しておく。 gorem: /home/mattn/dev/gorem/gorem -c /home/mattn/dev/gorem/config.json
my_app1: ruby /home/mattn/dev/my_app1/web.rb
my_app2: ruby /home/mattn/dev/my_app2/web.rb
gost: /home/mattn/dev/gorem/gost -c /home/mattn/dev/gost/config.json
また外部からのアクセスに対してリバースプロキシ経由でアクセスされる様に gorem の設定にも追加する。 { "path": "/deploy/", "backend": "http://localhost:11613" }, ※設定は一部

gost の設定ファイルは以下の様に記述する。 {
  "addr""127.0.0.1:11613",
  "apps": {
    "wikipedia-lingrbot": {
      "proc""wikipedia_bot",
      "path""/home/mattn/dev/wikipedia_bot/",
      "build_command""go build",
      "test_command""go test"
    },
    "momochan": {
      "proc""momochan",
      "path""/home/mattn/dev/momochan/"
    }
  }
}
あとは github の Web hook を上記の /deploy/ に仕向けて完成。 gost は Web hook のペイロードを受け取ると goreman に対して停止、git pull、起動を行う。もしビルドが必要であれば上記の様に build_command を指定する。その他 test_commandrelease_command もある。 上記の wikipedia-lingrbot は github のリポジトリ名、proc で指定する wikipedia_bot は goreman の Procfile に書いた proc 名となる。 path はそのリポジトリがあるローカルパスを指定し、そこで git pull が行われる。なお git clone 時に git@github.com で clone してると認証が必要になるので https://github.com/ で始まる URL で clone しておく事。
Posted at by