2009/12/31


最近、GoのGTKバインディングを作ってるのだけど、先日ようやく簡易twitterクライアントを作れるまでに至ったのだが、そのtwitterクライアントの「タイムライン更新」ボタンを押した時に画面をブロックさせずに画面を更新する方法を考えてた。
Perl-GTKならばCoroを使ってこうするだろうか。 use strict;
use warnings;
use Gtk2 '-init', '-threads-init';
use AnyEvent;
use Coro;
use Coro::Timer;

my $window = Gtk2::Window->new('toplevel');
$window->signal_connect(
    destroy => sub {
        exit;
    }
);
my $vbox = Gtk2::VBox->new;

my $button = Gtk2::Button->new('click me');
$button->signal_connect(
    clicked => sub {
        $button->set_sensitive(0);
        async {
            Coro::Timer::sleep 3;
            Gtk2::Gdk::Threads->enter;
            $button->set_sensitive(1);
            Gtk2::Gdk::Threads->leave;
        };
    }
);
$vbox->add($button);

$window->add($vbox);
$window->show_all;

Gtk2->main;
ここでCoro::Timer::sleepしているのは、生のsleepを使うとCoroがスケジュール割り当てとして介入出来なくなるのを回避する為。非同期で動作はするものの、スレッド動作になるので「Gtk2::Threads->enter」と「Gtk2::Threads->leave」でUIに関連する処理をブロックしなければなりません。つまり例えばボタン押下時の処理が5段階あり、その都度UIの何かしらを変更しなければならない場合、「Gtk2::Threads->enter」と「Gtk2::Threads->leave」でそれぞれブロックしなければならない事になります。
平たく言うと、スレッドを使う予定の無かったUI処理を、スレッド対応にするには多少のコード修正が必要になるという事です。
今回、goで作った簡易twitterクライアントの「タイムライン更新」ボタンを非同期対応するのに費やしたコードはたった2行。元のコード
    button.Clicked(func(w *gtk.GtkWidget, args []unsafe.Pointer) {
        button.SetSensitive(false);
        if r, _, err := http.Get("http://twitter.com/statuses/public_timeline.json"); err == nil {
            // タイムライン取得、表示処理
        }
        button.SetSensitive(true);
    }, nil);
となっていたコードに「go」で実行されるブロックを追加しただけ。
    button.Clicked(func(w *gtk.GtkWidget, args []unsafe.Pointer) {
        button.SetSensitive(false);
        go func() {
            if r, _, err := http.Get("http://twitter.com/statuses/public_timeline.json"); err == nil {
                // タイムライン取得、表示処理
            }
            button.SetSensitive(true);
        }();
    }, nil);
何が言いたいのかというと、groutineはgo組み込みであって、Coroがsleepやhttp_getを置き換えようとしている(CORE::GLOBAL上書きもあるけど)物とは違い、言語として非同期をサポートしているという事。もちろんgoにおいてもボタンクリック時にsleepを呼ぶとUIはブロックされるのだけど、実際sleepなんて使わないし、そもそもgoroutineはスレッドじゃない。
そう、goroutineはスレッドじゃない。
A Tutorial for the Go Programming Language

Go has its own model of process/threads/light-weight processes/coroutines, so to avoid notational confusion we call concurrently executing computations in Go goroutines.

http://golang.org/doc/go_tutorial.html
実際にはgoが内部でscheduleを行って実行する処理連鎖で実現している。なのでgtkを呼び出す場合においてもgdk_threads_enter/gdk_threads_leaveを呼び出す必要もない。なぜなら並行とは言えど同時実行ではないのでコンテキストスイッチも意識しなくて良い。
故に上記の様な 「go func(){}()」ブロックだけで非同期処理が書けるという訳。例えばcursesでCUIアプリを書いても画面が崩れる事は無いだろう(未確認)。

UIの開発に有利な言語にはある程度、共通する物があると思う。
  • 匿名関数をコールバックとして扱える
  • サードパーティでも良いので非同期処理の仕組みがある
  • GCが効く
この内、2個か3個あてはまるとUIを扱う上で有利になると思う。Javascriptはこの点(組み込みで非同期が扱える:というかそういうものしか扱えない?)素晴らしいし、jQueryを使えばAjax/UIとして作り手側はかなりの恩恵が得られる。Perlは「sub {}」で匿名関数が使えるがので優秀だが非同期となるとスレッドに頼らざるを得ないのが難点。rubyも同様。pythonは匿名関数が使えないのが少し悲しい(でもpygtkは好きだ)。結局の所、メインループと非同期処理をどう扱うかがネックになっている。例えば各言語でCOMを扱う場合、STA(Single Thread Apartment)しか対応していないCOMは、言語側でメインループをグルグル回すしかない。まぁ言語として用途が違うんだ(JavascriptはそもそもUI記述言語だろ...とか)から、比べちゃならんだろ...とも言えるし。
goにおいては上記3点あてはまるが、デストラクタが無いのが一番の難点。
これからのUIはどんどん非同期な物になって行くんだろうな。

10年程前のUIと言えば、非同期なUIはあまり見られなかった。VB6においても定期的に「DoEvents」を実行してプログレスバーを更新するという、なんちゃって非同期処理が横行していた頃だ。最近はVB.NETでコンポーネント自身がスレッドサポート(実際にはデリゲート)しているので簡単に非同期処理が書ける様になったが、これら全てにおいてもやはり
非同期対応するつもりの無かったUI処理を、非同期対応にするには多少のコード修正が必要になる
には違い無いし、その点でgoには期待している。
Posted at by



2009/12/22


Go binding for GTK GoのGTKバインディングを現在作業中なのですが、ようやく簡単なTwitterクライアントくらいならば作れる程度に出来上がってきたのでご報告。
mattn's go-gtk at master - GitHub

Go binding for GTK

http://github.com/mattn/go-gtk
イベントハンドラがJavaScriptっぽく書けるので、結構見通し良いコードになります。
package main

import "gtk"
import "gdk-pixbuf"
import "unsafe"
import "http"
import "json"
import "io"
import "os"
import "strconv"
import "strings"
import "reflect"

func url2pixbuf(url string) *gdkpixbuf.GdkPixbuf {
    if r, _, err := http.Get(url); err == nil {
        n, _ := strconv.Atoi64(r.GetHeader("Content-Length"));
        t := r.GetHeader("Content-Type");
        b := make([]byte, n);
        io.ReadFull(r.Body, b);
        var loader *gdkpixbuf.GdkPixbufLoader;
        if strings.Index(t, "jpeg") >= 0 {
            loader, _ = gdkpixbuf.PixbufLoaderWithMimeType("image/jpeg");
        } else {
            loader, _ = gdkpixbuf.PixbufLoaderWithMimeType("image/png");
        }
        loader.SetSize(24, 24);
        loader.Write(b);
        loader.Close();
        return loader.GetPixbuf();
    }
    return nil;
}

func main() {
    gtk.Init(&os.Args);
    window := gtk.Window(gtk.GTK_WINDOW_TOPLEVEL);
    window.SetTitle("Twitter!");
    window.Connect("destroy", func(w *gtk.GtkWidget, args []unsafe.Pointer) {
        gtk.MainQuit();
    }, nil);

    vbox := gtk.VBox(false, 1);

    scrolledwin := gtk.ScrolledWindow(nil, nil);
    textview := gtk.TextView();
    textview.SetEditable(false);
    textview.SetCursorVisible(false);
    scrolledwin.Add(textview);
    vbox.Add(scrolledwin);

    buffer := textview.GetBuffer();

    tag := buffer.CreateTag("blue", map[string] string {
        "foreground": "#0000FF", "weight": "700" });
    button := gtk.ButtonWithLabel("Update Timeline");
    button.Clicked(func(w *gtk.GtkWidget, args []unsafe.Pointer) {
        if r, _, err := http.Get("http://twitter.com/statuses/public_timeline.json"); err == nil {
            n, _ := strconv.Atoi64(r.GetHeader("Content-Length"));
            b := make([]byte, n);
            io.ReadFull(r.Body, b);
            j, _ := json.Decode(string(b));
            arr := reflect.NewValue(j).(*reflect.SliceValue);
            for i := 0; i < arr.Len(); i++ {
                data := arr.Elem(i).(*reflect.InterfaceValue).Elem().(*reflect.MapValue);
                icon := data.Elem(reflect.NewValue("user")).(*reflect.InterfaceValue).Elem().(*reflect.MapValue).Elem(reflect.NewValue("profile_image_url")).(*reflect.InterfaceValue).Elem().(*reflect.StringValue).Get();
                var iter gtk.GtkTextIter;
                buffer.GetStartIter(&iter);
                buffer.InsertPixbuf(&iter, url2pixbuf(icon));
                name := data.Elem(reflect.NewValue("user")).(*reflect.InterfaceValue).Elem().(*reflect.MapValue).Elem(reflect.NewValue("screen_name")).(*reflect.InterfaceValue).Elem().(*reflect.StringValue).Get();
                text := data.Elem(reflect.NewValue("text")).(*reflect.InterfaceValue).Elem().(*reflect.StringValue).Get();
                buffer.Insert(&iter, " ");
                buffer.InsertWithTag(&iter, name, tag);
                buffer.Insert(&iter, ":" + text + "\n");
            }  
        }
    }, nil);
    vbox.PackEnd(button, false, false, 0);

    window.Add(vbox);
    window.SetSizeRequest(300, 400);
    window.ShowAll();
    gtk.Main();
}
実行すると以下の様な画面になります。
go-gtk-twitter-client
まだ、完成じゃないです。がんばります!
Posted at by



2009/12/13


やっぱり少し私とは認識が違う気がする。
おごちゃんの雑文 » Blog Archive » 「はまちちゃん」をspamと断じる思考停止
この穴を「カンチョウ」で済ませているところが、はまちちゃんのサジ加減なのだ。

「スキだらけの後ろ」があることは、彼にはわかっている。わかっていてそこを突かないのは一見親切で礼儀正しい態度に見える。でも、そういった非常に突きやすい、単純な穴があることは、わかる人にはわかってしまっていて、気がついてないのは運営者だけという状態になっているのが、「Amebaなう」だったわけだ。はまちちゃんのやったのは「カンチョウ」かも知れないけれど、やれることがわかっている悪意の者であれば、

後ろから刺す

ことだって出来るわけだ。「悪の組織」がはまちちゃんの彼女をどーこーしたら… とかだってないとは言えない。Amebaには首相官邸のブログだってあるわけで、ポストにちょっとした仕掛けを入れるだけで、

公式チャネルからデマを流す

ことだって可能になる。仕手筋とかなら5分デマが流れりゃ十分満足だろう。Amebaには日本の最終兵器とも言われるデスブログだってあるわけでw
http://www.nurs.or.jp/~ogochan/essay/archives/2156

カンチョーで済ましてあげてるだけ、やさしい?

違うでしょ、肩をトントンと叩いて「ちょっと、スキだらけですよ。世の中ひどい人ばかりなので、気をつけないとカンチョーされますよ。」と言ってあげるのがやさしいんじゃないの?
おまけに傍観者とし面白い話かもしれないけどカンチョーでどんだけ人が動くか想像してもいいんじゃないかな?復旧にはSE一人じゃなかったかもしれないし、対策検討会の様な物もあったかもしれない。SE一人終日動かせば1日で原価数万飛びますよ。それ十分被害でしょ。
カンチョーがどんな印象を与えたのかは受けた者でないと分かんないよ。
はまちちゃんと脆弱性報告のあり方 - 世界線航跡蔵
これは「隙があった」んじゃないだろう。「開腹したまま内臓が露出している」んだ。

ところが、どうも現実の医師とは違ってこの世界の、特にAmebaみたいな大きな会社の開発者はその辺の意識が甘い。「手術したけど、まー、内臓が見えててもすぐに死ぬ訳じゃないし、適当に皮被せておけばいいよね」とか思ってる。下手したら開腹後に塞ぐべきという常識的なことを考えすらしない。で、そこら中を内臓が見えたままの患者が歩いている。
http://yugui.jp/articles/851
それも違う気がする。
この件で傍観している人たちは技術者だ。そしてその脆弱性は技術者でしか分からない。これはいわば院内感染ですよ。脆弱な病棟に気付かず通院/入院している一般の人達と、そこに「院内感染だー!」と叫ぶ人でしょ。彼らにはそれが冗談なのか何なのか分かってない。確かに今回のはまちちゃんの行為を「カンチョウ」と命名したのは私だが、このカンチョウで不安になった人がいたかもしれないし、Amebaの印象を悪くした人もいるかもしれない。

カンチョーで痔になってたら軟膏塗ってくれるの?はたまたカンチョーで筋うんこ付いてたら、パンツ洗ってくれるの?
カンチョーだってやり方次第では十分犯罪ですよ。


ただし言いっておきたいのは、私はこういったサービス全て注意勧告してあげるべきとは思ってない。注意して効かなかったサービスは勝手に潰れるだろうし、言い方変えれば潰れるべきだと思う。
私は脆弱性を見つけて注意勧告する事はとても大切だと思うが、それを手法交えて公に広める事については賛同出来ない。セキュリティを扱う人たちにとってはそれがステータスかもしれないけし、知名度を上げるチャンスなのかもしれないけど、私はそんなチャンスは欲しくないし、それで知名度を上げられたとしても嬉しくない。

私ならばもっと技術者らしい知名度の上げ方をする。ただそれだけの話ですよ。

ちなみに全然関係ない話ですが、私が中学生だった時の友達は、「起立!礼!着席!」のタイミングで前に座っていた同級生にカンチョウを食らわそうとしてたが、思っていた以上にズボンが固かった為、中指第二関節を骨折した事がありますよ!カンチョウには十分気をつけましょう。
Posted at by