Hatena::ブログ(Diary)

taknb2nchのメモ

2013-12-31

2013年の簡単まとめ

23:57

もう今年も時間が少なくなってしまったので簡単にだけまとめておきます。

2012年年末から家庭の事情でほぼ活動を停止していましたが4月ころから少しずつ活動を再開しました。

勉強会を主催したり参加したりしましたが、今年は大きく分けて2つありました。

一時期勉強会が各地で乱立されていましたが、淘汰されたのか日程がかぶることも少なくなってきたように思います。

今後どうなっていくか分かりませんが、必要な会には参加する、あるいは主催する、ご依頼をいただければ登壇するというスタンスは変わりないと思います。


来年もよろしくお願いいたします。

Golang Cafe #10 まとめ imageパッケージをみる

| 23:37

2013/12/29に開催された「Golang Cafe #10」についてのまとめです。

今回も参加する予定ではなかったのですが、時間がとれたので参加しました。

今回はimageパッケージを触ってみました。

+TakashiYokoyama氏が用意してくれたサンプルコードはこちらになります。


PNGの画像を読み込んでJPEGに変換する

package main

import (
    "image"
    "image/png"
    "image/jpeg"
    "os"
)

func main() {
    var inFile *os.File
    var outFile *os.File
    var img image.Image
    var err error

    if inFile, err = os.Open("pkg.png"); err != nil {
        println("Error", err)
        return
    }

    defer inFile.Close()

    if img, err = png.Decode(inFile); err != nil {
        println("Error", err)
        return
    }

    if outFile, err = os.Create("pkg.jpg"); err != nil {
        println("Error", err)
        return
    }

    option := &jpeg.Options{Quality: 100}

    if err = jpeg.Encode(outFile, img, option); err != nil {
    //if err = jpeg.Encode(outFile, img, nil); err != nil {
        println()
        return
    }

    defer outFile.Close()
}

手順は以下のとおり。

  1. 対象ファイルをオープンする
  2. オープンしたファイルポインタをDecodeメソッドに渡す->画像を表すImage型(あえて言うなら抽象型)として取得できる
  3. 出力先のファイルポインタと取得したImage型をEncodeメソッドに渡す

パッケージを確認する限りでは、gifjpeg、およびpng間で相互変換できると思われる。

また、Image型は取得系メソッドしか定義されていないため変更することができない。


PNGの画像を読み込んで編集する

先ほどの例で読み込んだ画像を編集することが難しいと書きましたが、Go言語デフォルトのパッケージでは一筋縄にはいきません。

+TakashiYokoyama氏のサンプルから説明すると、

rect := inImage.Bounds()
rgba := image.NewRGBA(rect)
size := rect.Size()

for x := 0; x < size.X; x++ {
    for y := 0; y < size.Y; y++ {
        rgba.Set(x, y, inImage.At(x, y))
    }
}
// image/drawパッケージをインポートして以下のようにしてもよい
//draw.Draw(rgba, rgba.Bounds(), inImage, image.Point{0, 0}, draw.Src)

for i := 0; i < 10; i++ {
    rgba.Set(60, (10 + i), image.Black.At(0, 0))
    rgba.Set(65, (10 + i), image.Black.At(0, 0))
    rgba.Set((58 + i), 13, image.Black.At(0, 0))
    rgba.Set((58 + i), 16, image.Black.At(0, 0))
}

// RGBA型をそのままDecodeメソッドに渡せるので以下の2行は不要です
//outRect := image.Rect(0, 0, rect.Max.X, rect.Max.Y)
//outImage := rgba.SubImage(outRect)

読み込んだImage型をSetメソッドが存在するRGBA型等にコピーします。簡単なコピーメソッドは存在しないのでピクセル単位でコピーする必要があります。

またDrawLineやDrawRectangle、あるいはDrawStringのようなメソッドは用意されていないので、自力でピクセルデータを編集する必要があります。


draw2dパッケージを試す

先ほどの例で書きましたが、Go言語のデフォルトのパッケージではDrawLineやDrawRectangle、あるいはDrawStringのようなメソッドは用意されていません。

GoDocでgraphicsをキーワードに検索したところ多くのパッケージが存在するようです。

今回はその中からdraw2dパッケージを試してみることにしました。

まずはパッケージをダウンロードします。

$go get code.google.com/p/draw2d/draw2d

次に先程の例でもおこなったように編集用にrgba型を作成し値をコピーし、その値を元にImageGraphicContext型を作成します。ImageGraphicContext型にはよく見るDraw系のメソッドが用意されているのでこれらを使用して編集することができます。

// import に "code.google.com/p/draw2d/draw2d" を追加

rgba := image.NewRGBA(inImage.Bounds())

draw.Draw(rgba, rgba.Bounds(), inImage, image.Point{0, 0}, draw.Src)

gc := draw2d.NewGraphicContext(rgba)
gc.MoveTo(10.0, 10.0)
gc.LineTo(100.0, 10.0)
gc.Stroke()
//gc.SetFont(f)
//gc.FillString("aaaaa")

最後の2行はコメントアウトしていますが、便利そうに見えるdraw2dパッケージですが文字列の描画はうまく動作しないようです。(draw2dパッケージのresource/fontディレクトリの内容をコピーするか、フォントディレクトリをして指定する)

ソースコードを確認していくと、読み込むフォントファイルのファイル名を生成する部分で妙な固定文字列が使用されており、フォントファイル名を編集するか、ソースコードを修正しなくてはならなさそうです。

さらには仮にフォントファイルを読み込めたとしてもパッケージ内でフォントファイルの内容をチェックしている部分があり、このチェックに引っかかりうまく読み込めないようです。(WindowsのFontディレクトリを指定してみましたがうまく動作しませんでした)

というわけでこのパッケージも万能というわけではなさそうです。

外にも似たようなパッケージがあるかもしれないので探してみても良いかもしれません。


まとめ

今回はImageの処理を試してみたわけですが、既存の言語と比較するとまだ若干弱いような気もしました。ただ画像を編集するような重い処理は行うなということかもしれません。(メッシュで分けてgoroutineで処理するとかやってみたいですね)

外部パッケージもありますがどういうことができるのかを検証するのにも時間が掛かりそうです。

今後の機能追加に期待したいです。

Golang Cafe #9 まとめ Go言語でデザインパターン

| 19:19

2013/12/22に開催された「Golang Cafe #9」についてのまとめです。

本来は参加する予定ではなかったのですが、時間がとれたので参加しました。

今回は

の内容をGo言語で書いてみるというものでした。

サンプルコード+TakashiYokoyama氏が用意してくれました。

時間にも限りがあり、singlethreadexecution、immutable、およびguardedsuspensionの3つのみの検証となりました。


singlethreadexecution

複数スレッドから同じ値を参照すると整合性がとれなくなってしまうというもの。

メイン関数にruntime.GOMAXPROCS(2)を追加して実行すると下記のように表示されます。

(私のノートPCで実行すると下記のように出力されるのですが、私のデスクトップMacチームは下記のような結果にはなりませんでした)

$ go run main.go
Testing Gate, hit CTRL+C to exit.
Alice BEGIN
Bobby BEGIN
Chris BEGIN
***** BROKEN ***** No.2157: Chris, Canada
***** BROKEN ***** No.2249: Bobby, Brazil
***** BROKEN ***** No.8642: Bobby, Brazil
***** BROKEN ***** No.10887: Chris, Canada
***** BROKEN ***** No.11187: Bobby, Brazil
***** BROKEN ***** No.13887: Chris, Canada
***** BROKEN ***** No.15086: Alice, Alaska
***** BROKEN ***** No.17209: Chris, Canada

これを防ぐにはどうしたらいいのか、参加者それぞれ方法がありましたが、私が一番スマートだと思った方法はこれです。


Gateにflagを追加します。

type Gate struct {
    counter int
    name string
    address string
    flag chan bool
}

Start関数のforループ内を修正します。

func(thread UserThread) Start() {
    println(fmt.Sprintf("%s BEGIN", thread.myName))
    for {
        <-thread.gate.flag
        thread.gate.Pass(thread.myName, thread.myAddress)
        time.Sleep(1 * time.Millisecond)
        thread.gate.flag <- true
    }
}

main関数のgate初期化のあとにflagを初期化します。

func main() {
    runtime.GOMAXPROCS(2)

    println("Testing Gate, hit CTRL+C to exit.")

    gate := Gate {counter: 0, name: "Nobody", address: "Nowhere"}
    gate.flag = make(chan bool, 1)
    gate.flag <- true

    // 以下は省略
}

Gate自体にバッファ1のチャンネル変数を追加することによりGateへの同時アクセスを制御しようというものです。

MutexのLock、Unlockを使用しても制御できそうですがこの方がスマートに思えました。


これで実行すると、下記のように正常に表示されます。

$ go run main.go
Testing Gate, hit CTRL+C to exit.
Alice BEGIN
Bobby BEGIN
Chris BEGIN

チャンネル変数はgoroutine間の値の受け渡しに使うだけでなく、バッファを持たせることでロックの代わりに使用できる典型的な例です。


immutable

実行結果の一例ですが下記のような内容続きます。

$ go run main.go
3 prints [ Person: name = Alice, address = Alaska ]1 prints [ Person: name = Alice, address = Alaska
 ]2 prints [ Person: name = Alice, address = Alaska ]


3 prints [ Person: name = Alice, address = Alaska ]
1 prints [ Person: name = Alice, address = Alaska ]2 prints [ Person: name = Alice, address = Alaska
 ]3 prints [ Person: name = Alice, address = Alaska ]

先ほどのサンプルでは一つの値に対して変更できる術があったがために不整合が起きてしまいましたが、このサンプルのように変更する方法がない場合には当然不整合は起きないというものです。


guardedsuspension

まず実行結果ですが以下のようになります。

$ go run main.go
ClientThread requests [ Request No. 0 ]
ServerThread handles [ Request No. 0 ]
ClientThread requests [ Request No. 1 ]
getRequest wait
ServerThread handles [ Request No. 1 ]
getRequest waitClientThread requests [ Request No. 2 ]

ServerThread handles [ Request No. 2 ]
getRequest waitClientThread requests [ Request No. 3 ]

ServerThread handles [ Request No. 3 ]
getRequest waitClientThread requests [ Request No. 4 ]

ServerThread handles [ Request No. 4 ]
getRequest waitClientThread requests [ Request No. 5 ]

ServerThread handles [ Request No. 5 ]

Clientのrequestに対してServerのhandle順次発生しています。


ポイントとなるのは以下の2つのメソッドです。

func (queue RequestQueue) getRequest() Request {
    queue.cond.L.Lock()
    if queue.queue.Len() <= 0 {
        // wait
        println("getRequest wait")
        queue.cond.Wait()
    }

    element := queue.queue.Front()
    request := element.Value.(Request)
    queue.queue.Remove(element)

    queue.cond.L.Unlock()
    return request
}
func (queue RequestQueue) putRequest(req Request) {
    queue.cond.L.Lock()
    queue.queue.PushBack(req)
    queue.cond.Signal()
    queue.cond.L.Unlock()
}

ClientがputRequestで値を設定したあとSignalメソッドを呼びます、するとgetRequestのWaitメソッドで待機していたServerの処理が再開されるというものです。

C#でいうとManualResetEventAutoResetEventのような使い方ですが、Waitメソッド、Signalメソッドを呼ぶ前後にLock、Unlockが必要なところに気をつけなくてはなりません。


簡単にまとめ

数ヶ月ほどGo言語に取り組んできましたが、やはり「goroutineとチャンネルをどう駆使するか」というところがポイントのように思えてきました。

まだまだ奥が深そうです。


年末と理由にしてはいけませんがまとめが遅くなってしまいました。

Connection: close