2016/07/13


並列ダウンローダを使うと幾らかサーバに負荷が掛かってしまいます。golang のサーバ側で帯域制限を行う場合には2つ方法があります。

  • 転送量制限する
  • 接続数制限する

まずは転送量制限。転送量の制限には throttled が便利です。

GitHub - throttled/throttled: Package throttled implements rate limiting access to resources such as HTTP endpoints.

README.md Throttled Package throttled implements rate limiting access to resources such as HTTP endp...

https://github.com/throttled/throttled
http.Handler 互換のミドルウェアなので既存のサーバに埋め込む形で使えます。 store, err := memstore.New(65536)
if err != nil {
    log.Fatal(err)
}

quota := throttled.RateQuota{throttled.PerMin(20), 5}
rateLimiter, err := throttled.NewGCRARateLimiter(store, quota)
if err != nil {
    log.Fatal(err)
}

httpRateLimiter := throttled.HTTPRateLimiter{
    RateLimiter: rateLimiter,
    VaryBy:      &throttled.VaryBy{Path: true},
}

http.ListenAndServe(":8080", httpRateLimiter.RateLimit(myHandler))

次に接続数制限。接続数制限には netutil.LimitedListener を使います。

netutil - GoDoc

package netutil import "golang.org/x/net/netutil" Package netutil provides network utility functions...

https://godoc.org/golang.org/x/net/netutil#LimitListener

こちらは net.Listener にかぶせるだけで最大接続本数を制限できます。

package main

import (
    "fmt"
    "golang.org/x/net/netutil"
    "log"
    "net"
    "net/http"
)

func main() {
    l, err := net.Listen("tcp"":8080")
    if err != nil {
        log.Fatal(err)
    }
    http.HandleFunc("/"func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "")
    })
    http.Serve(netutil.LimitListener(l, 100), http.DefaultServeMux)
}

今年の夏も暑そうですが、頑張って乗り切りましょう。

プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES) プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)
Alan A.A. Donovan, Brian W. Kernighan, 柴田 芳樹
丸善出版 単行本(ソフトカバー) / ¥4,180 (2016年06月20日)
 
発送可能時間:

Posted at by



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,180 (2016年06月20日)
 
発送可能時間:

Posted at by



2016/06/03


僕はもっぱらコマンドラインで作業するので peco を使う事が多いです。

Big Sky :: Windows のコマンドプロンプトを10倍便利にするコマンド「peco」

Windows ユーザのごく一部には、コマンドプロンプトが無いと生きられない民族がいます。そしてその民族の一部には cygwin や msys bash 等といった、サードパーティなシェル(power...

http://mattn.kaoriya.net/software/peco.htm
GitHub - mattn/pcd: peco + cd = awesome!

README.md pcd peco + cd = awesome! Requirements peco Windows Installation Copy pcd.bat into your fav...

https://github.com/mattn/pcd

ただユースケースとして peco だと若干大げさになる事があって、絞り込んだりしなくて良くただ単純に候補を選ぶだけでいいって事がたまにあります。例えば画面に表示された内容を見ながら候補を選びたいであったり、「以下の中から選んで下さい」といった説明のあとユーザに数個の候補から選ばせるといったバッチコマンドを作る場合です。peco だと画面が隠れてしまうんです。さらに peco は termbox-go を使っていて termbox-go のバグっぽい物を踏んだりするし、Windows でワイド文字 API を使っている手前ラスタフォントを使わないと画面が崩れる事があります。正直エスケープシーケンスを使えば候補を選ぶくらいなら出来るし、Windows でも go-colorable があればある程度のエスケープシーケンスは扱えると分かっていたので peco とは違う路線で薄いコマンドラインセレクタを作ってみました。

GitHub - mattn/cho

README.md cho choice! Why cho? Why not choice ? Because Windows already have choice command. Why not...

https://github.com/mattn/cho

はじめは「choice」というコマンド名で開発していたのだけど、よく考えたら Windows には choice コマンドが既にあって、「じゃぁ choic?」「いやいや」「では choi は?」「ダメだろ」「それなら cho だ!」くらいの感覚でネーミングしました。ただ適当に名付けた割にキーボードのホームポジションからそれ程手を動かさなくてもいいので実はちょっと気に入っています。使い方は peco と変わりません。ただし絞り込み機能は提供していませんので、ながーいファイルから1つ選びたいなら頑張って j/k するか大人しく peco を使って下さい。

cho

Windows、Linux、BSD、Mac OSX で動作します。go-colorable にいくらか修正を入れているので最新版を使ったほうがいいです。

おまけ

例えば Windows でコマンドを実行し cho で選んだ結果を変数に入れる様にしたい場合(UNIX の FOO=`ls | cho`)は以下の様なバッチファイル「setvar.bat」を用意しておくと便利です。

@echo off
for /f "delims=;" %%i in ('%2 ^| nkf -Sw ^| cho -cl ^| nkf -Ws') do set %1=%%i

nkf 持ってない人はコチラ(結果がUTF-8になっちゃうけど

@echo off
for /f "delims=;" %%i in ('%2 ^| cho -cl') do set %1=%%i

すると以下のコマンドで dir の出力結果を cho で選んだ内容が変数 foo に入ります。

setvar foo "dir /b"
Posted at by