2014/06/27


C言語でアプリケーションを書くのは他の言語と比べて少し気合が必要ですよね。例えば
  • HTTPからデータを取得する
  • 取得したデータを json パースする
  • 結果の一部を色付きで表示する
こんな場合、C言語プログラマは

「HTTP か、じゃぁcurlかな」
「JSON か、parson かな」
「色表示か...エスケープシーケンスでもいいけどWindowsがなー...」

といった事を考え、そこから curl や parson といった資材の調達を始める事になります。途中で新しい機能を追加したくなり、それを外部ライブラリに頼る場合だとその都度資材を調達する必要があり、思考を停止しなければなりません。
この辺は ruby や perl、nodejs、golang 等の様に、ちょっとした手間だけで済ませたい物です。
またC言語の場合、ヘッダファイルはシステムの include フォルダに提供元が期待する通りに配置するか、手元の Makefile でパスを通す必要があり、ライブラリもバージョンに従ったファイル名である必要があります。
こういうチマチマとした作業も楽しかったりはするのですが、今すぐ作りたいって時には腰が重い作業となります。include 配下を整理し出したらどうも気になり始めて、気付けば全く関係の無い事をやっていて作りたかった物が何も出来ていなかった、なんて事もありますよね。

以前 github で clib というプロジェクトを見つけました。
clibs/clib - GitHub

Package manager for the C programming language.

https://github.com/clibs/clib
言うなれば、C言語版の bundler であったり、carton であったり、npm だったりする訳です。

今日は、この clib を使うとどの様に開発が進められるかを手順と共に説明します。

package.json を用意する

上記の様な仕様を満たすライブラリは幾つかあります。また github 上には clib から扱えるライブラリが多く存在ます。そしてそのリポジトリには package.json というファイルが含まれています。
今回の仕様を満たす為に、僕は以下の package.json を作りました。
{
  "name""helloworld",
  "version""1.0.0",
  "repo""mattn/helloworld",
  "dependencies": {
    "stephenmathieson/http-get.c""0.1.0",
    "kgabis/parson""*",
    "Constellation/console-colors.c""1.0.1"
  },
  "install""make install"
}
追記: parson-repo が無くなってたので修正

これをリポジトリ直下に置いて以下のコマンドを叩きます。

clib install

clib install
deps フォルダ配下にパッケージがインストールされました。 ├── deps
│   ├── console-colors
│   │   ├── console-colors.c
│   │   ├── console-colors.h
│   │   └── package.json
│   ├── http-get
│   │   ├── http-get.c
│   │   ├── http-get.h
│   │   └── package.json
│   └── parson
│       ├── package.json
│       ├── parson.c
│       └── parson.h
└── package.json
簡単すぎる!!!まぁなんと便利なんでしょう!
ちなみにこの clib install は、使用するパッケージが依存する別のパッケージも合わせて持ってきてくれる為、依存物地獄に陥る心配もありません。

Makefile を用意する

まず src ディレクトリを作り、リポジトリ直下には以下の Makefile を置きます。 CC     ?= cc
PREFIX ?= /usr/local

ifeq ($(OS),Windows_NT)
BINS    = helloworld.exe
LDFLAGS = -lcurldll
CP      = copy /Y
RM      = del /Q /S
MKDIR_P = mkdir
else
BINS    = helloworld
LDFLAGS = -lcurl
CP      = cp -f
RM      = rm -f
MKDIR_P = mkdir -p
endif

SRC  $(wildcard src/*.c)
DEPS $(wildcard deps/*/*.c)
OBJS $(DEPS:.c=.o)

CFLAGS  = -std=c99 -Ideps -Wall -Wno-unused-function -U__STRICT_ANSI__

all: $(BINS)

$(BINS): $(SRC) $(OBJS) $(RES)
    $(CC) $(CFLAGS) -o $@ src/$(@:.exe=).c $(OBJS) $(RES) $(LDFLAGS)

%.o: %.c
    $(CC) $< -c -o $@ $(CFLAGS)

clean:
    $(foreach c, $(BINS), $(RM) $(c);)
    $(RM) $(OBJS)

install: $(BINS)
    $(MKDIR_P) $(PREFIX)/bin
    $(foreach c, $(BINS), $(CP) $(c) $(PREFIX)/bin/$(c);)

uninstall:
    $(foreach c, $(BINS), $(RM) $(PREFIX)/bin/$(c);)

test:
    @./test.sh

.PHONY: test all clean install uninstall
この Makefile を使うと、deps ディレクトリ配下にうまくパスを通してくれる為、以後 make コマンド一発で依存物がビルド出来る様になっています。

コードを書こう

コードは、江添さんのブログ「本の虫」の Blogger JSON Feed をパースし、タイトルと URL を一覧表示します。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "console-colors/console-colors.h"
#include "parson/parson.h"
#include "http-get/http-get.h"

int
main(int argc, char* argv[]) {
  http_get_response_t* res = http_get("http://cpplover.blogspot.com/feeds/posts/default?alt=json");
  if (res->status != 200) {
    cc_fprintf(CC_FG_RED, stderr"ERROR");
    goto leave;
  }

  char* json = calloc(res->size + 11);
  if (!json) goto leave;
  strncpy(json, res->data, res->size);

  JSON_Value *root_value = json_parse_string(json);
  JSON_Object *root = json_value_get_object(root_value);
  JSON_Array *entries = json_object_dotget_array(root, "feed.entry");
  for (int i = 0; i < json_array_get_count(entries); i++) {
    JSON_Object *entry = json_array_get_object(entries, i);
    cc_fprintf(CC_FG_BLUE, stdout"%s\n",
      json_object_dotget_string(entry, "title.$t"));
    JSON_Array *links = json_object_get_array(entry, "link");
    for (int l = 0; l < json_array_get_count(links); l++) {
      JSON_Object *link = json_array_get_object(links, l);
      if (!strcmp("alternate", json_object_get_string(link, "rel"))) {
        cc_fprintf(CC_FG_YELLOW, stdout"%s\n",
          json_object_dotget_string(link, "href"));
        break;
      }
    }
  }
  json_value_free(root_value);

leave:
  if (res) http_get_free(res);
  if (json) free(json);
  return 0;
}
ちょっと適当なのでバグあるかもしれませんがご愛嬌で。
実行結果は以下の通り。
本の虫
簡単すぎる!!!
僕の中ではちょっとしたライフチェンジングですね。
このスピード感なら色んな物が作れそうな気がしますね。実際このコードも clib install を含めて10数分程度で書けました。
皆さんも一度、このスピード感を味わってみて下さい。
Posted at by



2014/06/25


golang と言えば非同期に特化した言語ですが、慣れない内は簡単な非同期しか使えません。しかし sync パッケージを知る事でもっとカジュアルに、かつ確実な非同期処理を行う事が出来る様になります。 今日はそんな sync パッケージについて説明してみたいと思います。

sync.Mutex

ご存じ sync.Mutex です。皆さんが一番使う排他制御だと思います。

package main

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

func parallel(wg *sync.WaitGroup) {
    fmt.Println("博")
    time.Sleep(100 * time.Millisecond)
    fmt.Println("多")
    time.Sleep(100 * time.Millisecond)
    fmt.Println("の")
    time.Sleep(100 * time.Millisecond)
    fmt.Println("塩")
    wg.Done()
}

func main() {
    runtime.GOMAXPROCS(runtime.NumCPU())

    wg := new(sync.WaitGroup)
    for i := 0; i < 3; i++ {
        wg.Add(1)
        go parallel(wg)
    }
    wg.Wait()
}

このコードを実行すると結果はおおよそ以下の様になります。













時には順番が入り乱れる事もあるでしょう。これに sync.Mutex を追加して以下の様にします。

package main

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

func parallel(wg *sync.WaitGroup, m *sync.Mutex) {
    m.Lock()
    defer m.Unlock()

    fmt.Println("博")
    time.Sleep(100 * time.Millisecond)
    fmt.Println("多")
    time.Sleep(100 * time.Millisecond)
    fmt.Println("の")
    time.Sleep(100 * time.Millisecond)
    fmt.Println("塩")
    wg.Done()
}

func main() {
    runtime.GOMAXPROCS(runtime.NumCPU())

    wg := new(sync.WaitGroup)
    m := new(sync.Mutex)
    for i := 0; i < 3; i++ {
        wg.Add(1)
        go parallel(wg, m)
    }
    wg.Wait()
}

すると













期待通りの順番に表示されます。上記のコードでは表示の排他のみを扱いましたが、更新処理と参照処理により排他制御を区別出来る RWMutex もあります。

また、上記のコードでしれーっと書いていますが sync.WaitGroup は Wait() を呼び出すと Add() を呼び出した回数から Done() を呼び出した回数を引いて 0 になるまで待機する機能が簡単に実装出来ます。全ての goroutine の終了を待つ場合に使用します。

sync/atomic

sync/atomic パッケージは、名前の通りアトミックな演算を提供します。

package main

import (
    "fmt"
    "runtime"
    "sync"
)

var v int32

func main() {
    runtime.GOMAXPROCS(runtime.NumCPU())
    wg := new(sync.WaitGroup)
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            v++
            v++
            v++
            v++
            v++
            v++
            v++
            v++
            v++
            v++
            wg.Done()
        }()
    }
    wg.Wait()
    fmt.Println(v)
}

このコードを実行すると、10回の加算を10並行で行うため合計で100になる事が期待出来ますが実際には何回かに1回100でない事象が発生します。

演算がアトミックでないからです。こういう場合に sync/atomic を使います。

package main

import (
    "fmt"
    "runtime"
    "sync"
    "sync/atomic"
)

var v int32

func main() {
    runtime.GOMAXPROCS(runtime.NumCPU())
    wg := new(sync.WaitGroup)
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            atomic.AddInt32(&v, 1)
            atomic.AddInt32(&v, 1)
            atomic.AddInt32(&v, 1)
            atomic.AddInt32(&v, 1)
            atomic.AddInt32(&v, 1)
            atomic.AddInt32(&v, 1)
            atomic.AddInt32(&v, 1)
            atomic.AddInt32(&v, 1)
            atomic.AddInt32(&v, 1)
            atomic.AddInt32(&v, 1)
            wg.Done()
        }()
    }
    wg.Wait()
    fmt.Println(v)
}

このコードは何回実行しても合計が100になります。

sync.Once

ある関数ではある処理を1回のみ行いたい、そういった場合に使用します。

package main

import (
    "fmt"
    "runtime"
    "sync"
)

var once = new(sync.Once)

func greeting(wg *sync.WaitGroup) {
    once.Do(func() {
        fmt.Println("こんにちわ")
    })

    fmt.Println("ごきげんいかがですか")
    wg.Done()
}

func main() {
    runtime.GOMAXPROCS(runtime.NumCPU())

    defer fmt.Println("さようなら")

    wg := new(sync.WaitGroup)
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go greeting(wg)
    }
    wg.Wait()
}

このコードの実行結果は以下の様になります。

こんにちわ
ごきげんいかがですか
ごきげんいかがですか
ごきげんいかがですか
ごきげんいかがですか
ごきげんいかがですか
さようなら

並行で走った場合でも1度しか実行されません。

sync.Cond

例えば goroutine を先行で10個用意しておき、ある号令に従い1つずつ並行処理を開始したいとします。こういうケースでは sync.Cond を作り Signal() を1回ずつ呼び出します。

package main

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

func main() {
    runtime.GOMAXPROCS(runtime.NumCPU())

    l := new(sync.Mutex)
    c := sync.NewCond(l)
    for i := 0; i < 10; i++ {
        go func(i int) {
            fmt.Printf("waiting %d\n", i)
            l.Lock()
            defer l.Unlock()
            c.Wait()
            fmt.Printf("go %d\n", i)
        }(i)
    }

    for i := 0; i < 10; i++ {
        time.Sleep(1 * time.Second)
        c.Signal()
    }
    time.Sleep(1 * time.Second)
}

このコードを実行すると以下の様な出力になります。

waiting 0
waiting 9
waiting 5
waiting 1
waiting 2
waiting 3
waiting 4
waiting 8
waiting 6
waiting 7
go 0
go 9
go 5
go 1
go 2
go 3
go 4
go 8
go 6
go 7

また全 goroutine に対して一斉に号令を掛ける場合は Broadcast() を使います。

package main

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

func main() {
    runtime.GOMAXPROCS(runtime.NumCPU())

    l := new(sync.Mutex)
    c := sync.NewCond(l)
    for i := 0; i < 10; i++ {
        go func(i int) {
            fmt.Printf("waiting %d\n", i)
            l.Lock()
            defer l.Unlock()
            c.Wait()
            fmt.Printf("go %d\n", i)
        }(i)
    }

    for i := 3; i >= 0; i-- {
        time.Sleep(1 * time.Second)
        fmt.Println(i)
    }
    c.Broadcast()
    time.Sleep(3 * time.Second)
}

このコードを実行すると以下の様な出力になります。

waiting 0
waiting 1
waiting 4
waiting 7
waiting 8
waiting 9
waiting 5
waiting 2
waiting 3
waiting 6
3
2
1
0
go 0
go 9
go 1
go 4
go 7
go 8
go 3
go 5
go 2
go 6

カウントダウン後、「go」の部分が一気に出力されます。

簡略化の為に time.Sleep でカウントダウン待ちを入れていますが、実際は sync.Mutex により c.Broadcastc.Wait を追い越さないようにしなければなりません。

sync.Pool

golang 1.3 から追加された機能です。

例えば、逐次行う作業と非同期に割込みで入ってくる作業を上手く処理したいとします。

package main

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

func main() {
    // disable GC so we can control when it happens.
    defer debug.SetGCPercent(debug.SetGCPercent(-1))

    p := sync.Pool{
        New: func() interface{} {
            return "定時作業"
        },
    }

    wg := new(sync.WaitGroup)

    wg.Add(1)
    go func() {
        for i := 0; i < 10; i++ {
            p.Put("割込作業")
            time.Sleep(100 * time.Millisecond)
        }
        wg.Done()
    }()

    wg.Add(1)
    go func() {
        for i := 0; i < 10; i++ {
            fmt.Println(p.Get())
            time.Sleep(50 * time.Millisecond)
        }
        wg.Done()
    }()

    wg.Wait()
}

このコードでは、10個の「定時作業」(Getを行う途中に「割込作業」(Put)が割り込みます。作業を行うのは10個限りとします。

定時作業
定時作業
割込作業
定時作業
割込作業
割込作業
定時作業
定時作業
割込作業
定時作業

作業を割り込む側、作業を処理する側が並行で動いていても上手く処理出来ています。

この様に、golang の sync パッケージにはにとても便利な機能が揃っています。ぜひもっとカジュアルに非同期機能を使ってみて下さい。

きっと面白い物が出来上がると思います。

Posted at by



2014/06/19


golang で Web と言えば、net/http でハンドラ書いて http.ListenAndServe を呼び出すサーバ方式が思い浮かびますが、他にも選択はあるはずです。
mattn/go-cgi - GitHub
https://github.com/mattn/go-cgi
golang で CGI が書けます。

まず上記リポジトリを clone して go-cgi.exe を作ります。 git clone https://github.com/mattn/go-cgi
cd go-cgi
go build
次に「管理ツール」から「インターネットインフォメーションサービス」を起動し、サイトに仮想ディレクトリを足します。
IIS
「ハンドラーマッピング」を選び、一覧上を右クリックして「スクリプトマップの追加」を選択します。
スクリプトマップ
「要求パス」は *.go、「実行可能ファイル」に先程ビルドした go-cgi.exe へのパスを、「名前」に CGI-go を入力します。
要求の制限
「要求の制限」をクリックして、「要求のマップ先が次の場合のみハンドラーを呼び出す」にチェックを入れ、「ファイル」を選び、あとは「OK」をクリックします。

最後に画面左のツリーの最上部にあるPC名をクリックして「ISAPIおよびCGIの制限」を選び、一覧に表示される CGI-go を右クリックして「許可」に変更します。
ISAPIおよびCGIの制限

これで IIS 上で拡張子 go のファイルが CGI として実行出来る準備が整いました。
Apache/nginx で動作させる場合は、これから作る CGI の拡張子を cgi にしてファイルの先頭行に #!/usr/local/bin/go-cgi といった感じに shebang を書くと動きます。
なお、go コマンドへパスを通していない場合は、これから説明する CGI を置くディレクトリに .go-cgi というフォルダを作り、そこに env というファイルを作成します。そこに GOROOT=c:/go といった感じに GOROOT を教えてあげるおまじないを書くと動く様になると思います。

さて CGI を書きましょう。上記で仮想ディレクトリをマッピングしたディレクトリに移動し、例として foo.go というファイルを以下の様に作ります。
package main

import (
    "fmt"
)

func main() {
    fmt.Print("Content-Type: text/html;\r\n\r\n")
    fmt.Println("Hello World")
}
ブラウザから http://localhost/mattn/foo.go を開くと Hello World が表示されるかと思います。
go-cgi は /tmp/go-cgi(Windows だと %TEMP%\go-cgi)、もしくは CGI ファイルと同じディレクトリに .go-cgi というディレクトリを作り、そこにハッシュ値で管理された go ファイルを生成すると同時にコンパイルして実行しています。
なお、golang の net/http は CGI を書く場合でも http.Handler を使う事が出来ます。 #! go-cgi
package main

import (
    "fmt"
    "net/http"
    "net/http/cgi"
)

func main() {
    cgi.Serve(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type""text/plain; charset=utf-8")
        fmt.Fprintf(w, "Hello %s", r.FormValue("name"))
    }))
}
簡単ですね。Windows の皆さんもぜひ golang で Web やりましょう。
Posted at by