2016/07/06


golang の channel は他の言語に見ない独特のパラダイムを開発者に提供します。

単純にスレッド間でメッセージングをするだけでもC言語で書けばそこそこの量になったり、慣れていない人であればどう実装すればいいか分からないなんて事もあったと思います。しかし golang の goroutine/channel は、やっている内容の割にとても容易にスレッド間通信やキューイング、処理の受け待ち等を実装できる様になっています。尚、channel をどの様に適用したら良いかについては以下を参照下さい。

Big Sky :: Golang の channel の使い所

golang の特徴と言えば goroutine と channel ですが、その使いどころに悩む人もおられる様です。 goroutine は非同期に実行される処理、channel はその grout...

http://mattn.kaoriya.net/software/lang/go/20131112132831.htm

今日はその goroutine/channel を使ったテクニックを3つほど紹介したいと思います。

ポーリング

channel は一般的に goroutine 側で chan への送信を行い、メイン処理側で chan の受信を行う事が多いと思います。

package main

import (
    "time"
)

func main() {
    q := make(chan struct{})

    go func() {
        // 重たい処理
        time.Sleep(3 * time.Second)
        q <- struct{}{}
    }()

    // q に何か入るまで待つ
    <-q
}

しかしメイン処理側でも待ち時間でやりたい事があったりもします。そういった場合にもう一つ goroutine/channel を作って...という方法もアリですが実は channel のポーリングは len(q) を使って簡単に実装できます。len(q) はその時点で channel に溜まったキューの数を返します。アトミックです。

package main

import (
    "fmt"
    "time"
)

func main() {
    q := make(chan struct{}, 2)

    go func() {
        // 重たい処理
        time.Sleep(3 * time.Second)
        q <- struct{}{}
    }()

    for {
        if len(q) > 0 {
            break
        }

        // q に溜まるまで他の事をしたい
        time.Sleep(1 * time.Second)
        fmt.Println("何か")
    }
}

こうする事で、q が送信されるまで別の処理を実できる様になります。GUI のメッセージループと channel を組み合わせる場合などには有効な手段です。注意点としては make で channel を作成する際に、バッファ数を 2 以上に設定しなければならない点です。そうしないと len(q) は常に 0 を返し続けます。

キューイング

例えば channel の受信をトリガにしてデータベースに接続して更新処理を実行する処理を実装するとします。複数のトリガが同時に発生した場合、channel には更新リクエストが溜まっていく訳ですが出来れば1回のデータベース接続で溜まった分全ての更新リクエストを処理したいといったケースもあります。あり得るケースで言えばデータベース接続が遅いなど。そこで登場するのが time.After との組み合わせです。

package main

import (
    "fmt"
    "time"
)

func main() {
    q := make(chan string5)

    go func() {
        time.Sleep(3 * time.Second)
        q <- "foo"
    }()
    go func() {
        time.Sleep(3 * time.Second)
        q <- "bar"
    }()

    // ほぼ同時に2つトリガが発生するのが分かっている
    var cmds []string
    cmds = append(cmds, <-q) // まずは一つ受信する
wait_some:
    for {
        select {
        case cmd := <-q:
            // 1秒以内なら一緒に処理しちゃうよ
            cmds = append(cmds, cmd)
        case <-time.After(1 * time.Second):
            // 1秒過ぎたらもう受け付けないよ
            break wait_some
        }
    }
    for _, cmd := range cmds {
        fmt.Println(cmd)
    }
}

この様に time.After が作る1秒間タイマー(実際は channel)とコマンド要求 channel を同時に待つ事で簡単に実装できます。これを他の言語で実装しようと考えるとちょっと頭がクラッとしてしまうかもしれませんね。

ワーカー

goroutine/channel を使えばワーカースレッドも簡単に作れます。例えば仕事量が5個あり、それを3つのワーカースレッドで仕事を取り合う物を実装したい場合は以下の様に実装します。

package main

import (
    "fmt"
    "sync"
    "time"
)

func fetchURL(wg *sync.WaitGroup, q chan string) {
    // 注意点: ↑これポインタな。
    defer wg.Done()
    for {
        url, ok := <-// closeされると ok が false になる
        if !ok {
            return
        }

        fmt.Println("ダウンロード: ", url)
        time.Sleep(3 * time.Second)
    }
}
func main() {
    var wg sync.WaitGroup

    q := make(chan string5)

    // ワーカーを3つ作る
    for i := 0; i < 3; i++ {
        wg.Add(1)
        go fetchURL(&wg, q)
    }

    // 同時には3つまでしか処理できない
    q <- "http://www.example.com"
    q <- "http://www.example.net"
    q <- "http://www.example.net/foo"
    q <- "http://www.example.net/bar"
    q <- "http://www.example.net/baz"
    close(q) // これ大事

    wg.Wait() // すべてのgoroutineが終了するのをまつ
}
channel の受信処理 url, ok := <-q は channel が閉じられると2つ目の戻り値に false を返します。なので各ワーカースレッドはそれが true である間は channel からジョブを受け取って処理すれば良い事になります。

可能性はまだまだある

goroutine/channel の可能性はまだまだあると思っています。軽量なのでどんどん使っていけば良いですし、「こんな簡単な物に channel を使うのは大げさだ」と考える必要もないと思います。例えば peco では内部で大量に channel が使われていますがキビキビと動作します。CUI と golang の channel については昔に書かせて頂いた Gopher Academy の Advent Calendar 2013 でも紹介しています。

まだまだ channel を使った技はありそうなので、イイカンジのテクニックが出来たらぜひ情報発信して皆で共有して欲しいです。

プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES) プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)
Alan A.A. Donovan, Brian W. Kernighan
丸善出版 / ¥ 4,104 (2016-06-20)
 
発送可能時間:在庫あり。


2016/06/25


ネタを振られたら答えるのが Vimmer!そんなクマに釣られてワナー!

GitHub - mattn/vim-soundcloud
https://github.com/mattn/vim-soundcloud
vim-soundcloud

CtrlP 拡張だし、ffplay(もしくは avplay)、mplayer のどれかが入ってないと動きませんが、よろしければどうぞ。


2016/06/20


SPA でアプリケーションを作る際、angular1 の時はどうも「書かされている」感がして好きになれませんでした。x-tag を使って自前でカスタムエレメントを作っても良いのですが、それでも書かされている感がしますし、若干大げになりがちです。そんな中、angular 寄りで、かつ x-tag よりも薄い JavaScript ライブラリを tokuhirom がガシガシと作ってくれました。

GitHub - tokuhirom/sj.js

Tiny javascript view for custom elements based on incremental-dom. This library supports angular1 like templating.

https://github.com/tokuhirom/sj.js

x-tag の様に js 一つで動きます。そしてだいたい angular っぽく動きます。動作はコチラで確認して下さい。

<div sj-app="">
 
<p>Input something in the input box:</p>
<p>Name : <input type="text" sj-model="this.name" placeholder="Enter name here"></p>
<h1>Hello <span sj-bind="this.name"></span></h1>

</div>

まずこのソースの短さを見て下さい。コードは何も書いてません。機能的には angular のそれと同じですがモデル(sj-model)やバインド(sj-bind)を指定する際に this が必要という事が異なります。angular や riot.js の場合は独自に式をパースし実行ていますが、そこをスコープに依存した JavaScript 式で書ける様にしてあります(元々は式パーサも実装していましたがコードベースが肥大してきたのでこの方式に転換)。またテンプレートは angular の様に {{ name }} という書き方にする事は出来ません。以下の様にノードを用意する必要があります。

<h1>Hello <span sj-bind="this.name"></span></h1>

当初はこの {{ name }} というテンプレートを実装していましたが、サーバサイドテンプレートとの相性が良くない(間違って使った場合に意図しない eval 発火が起きる)のを理由に、バインド方式に変更されました。

イベントハンドラも一通り揃っています。

<script>
function CounterApp() {
  this.counter = 0;
  this.count = function() {
    this.counter++;
    this.update();
  }
}
</script>

<div sj-app="CounterApp">
<button sj-click="this.count()">Click Me!</button>
<p sj-bind="this.counter"></p>
</div>

this は sj-app 属性を持ったのノードを指します。試しに動かしてみたい方はコチラで確認して下さい。

また sj-app にはコールバック関数名を指定できるので初期化を行う事もできます。自前でデータを更新したい場合は update() を呼び出します。もちろん repeat も動きます。その他のサンプルはココから参照して下さい。

また sj ではカスタムタグを生成する事も可能です。

customElements.define('sj-books'class extends sj.Element {
  template() {
    return `
      <h3>Books</h3>
      <input type="text" sj-model="this.filter" placeholder="検索するキーワードを入力して下さい" class="books-filter" />
      <input type="button" sj-disabled="!!!this.filter" sj-click="this.clear()" value="クリア" />
      <div class="books-container">
        <div sj-repeat="x in this.books">
          <div class="item" sj-if="this.matched(x,this.filter)" sj-click="this.clicked($index)">{{x.name}}</div>
        </div>
      </div>
    `;
  }

  initialize() {
    this.books = [];
    this.clear = () => {
      this.filter = '';
      this.update();
    };
    this.clicked = (index) => {
      const URI = 'http://www.amazon.co.jp/gp/search/';
      location.href = URI + `?field-keywords=${encodeURIComponent(this.books[index].name)}`;
    };
    this.matched = (x,filter) => !!!filter || x.name.toLowerCase().indexOf(filter.toLowerCase()) != -1;
  }

  get books() {
    return this.b;
  }

  set books(b) {
    this.b = b;
    this.update();
  }
});

この例では書庫検索画面を sj-books という Web Component に仕上げています。動作を確認したい人はコチラで確認できます。コードを見て貰えると sj の薄さ、そして「書かされている感」がそれほどしない事が分かって頂けると思います。

angular 程色んな事が出来る訳ではありませんが、アプリケーションを作るには十分機能がそろったライブラリになっています。ライセンスは MIT です。

AngularJS アプリケーションプログラミング AngularJS アプリケーションプログラミング
山田 祥寛
技術評論社 / ¥ 3,996 (2015-08-19)
 
発送可能時間:在庫あり。