Edited at

Go言語でターミナルニコ生コメントビューワー kome 作った

More than 3 years have passed since last update.


komeとは

https://github.com/kroton/kome


kome is Terminal Niconama Comment Viewer



  • ターミナルで動くニコニコ生放送用のコメントビューワーです。

  • コメントの送受信、閲覧ができます。

  • 操作はキーボードのみ、vim風のキーバインドを採用しています。

  • macとlinuxだったら動くと思います。(windowsは未検証)


作った経緯

peco経由でtermbox-goを知る。これで何かつくってみたい。



tigのようなグラフィカル&インタラクティブなCUIいいなぁ。



フォロワーさんでコメビュ(Viqo)作っている人がいたので乗っかる。


得られた知見


デッドロックは簡単におきる

channelのデッドロックは結構わかりずらくて3日くらい悶々と悩まされました。

例えばこんなコード

up := make(chan int, 10)

down := make(chan int, 10)

// upstream
go func(){
for {
n := <-up
if n % 2 == 0 {
fmt.Println(n)
continue
}
down <- n
}
}()

// downstream
go func(){
for {
n := <-down
up <- n * 2
}
}()

for i := 0; i < 50; i++ {
up <- (i * 2 + 1)
}

上流に流れた数字は偶数であれば出力されて終了、奇数であれば下流に流され下流で2倍にされてまた上流に戻ってくる。

これを実際動かすとデッドロックを起こしてしまいます。

channelのバッファが埋まってしまえば結局ブロッキングされてしまうので、こういうループ構造になってる場合は注意が必要そうです。


キャンセル力

コメントの送受信はソケット通信で行われます。ソケットからの読み込みはブロッキングするのでkomeでは次のようなgoroutineにしています。

go func(){

for {
n, err := socket.Read(buf)
if err != nil {
return
}
// ....
// buf から commentを作って外に投げる
out <- comment
}
}()

このgoroutineを外から止めたい場合どうすればいいか。

まず、socketを閉じればReadでエラーが起きるのでこれを使います。

quit := make(chan struct{}, 1)

go func(){
defer func(){
quit <- struct{}{}
}()

for {
n, err := socket.Read(buf)
if err != nil {
return
}
// ....
// buf から commentを作って外に投げる
out <- comment
}
}()

// close処理
socket.Close()
<-quit

これで完成ではありません。

out <- comment の部分がブロッキングするかもしれないので、終了シグナル用の channel と select を追加します。

quit := make(chan struct{}, 1)

sig := make(chan struct{}, 1)

go func(){
defer func(){
quit <- struct{}{}
}()

for {
n, err := socket.Read(buf)
if err != nil {
return
}
// ....
// buf から commentを作って外に投げる
select {
case out <- comment:
case <-sig:
return
}
}
}()

// close処理
socket.Close()
sig <- struct{}{}
<-quit

このsigを複数goroutineだったり複数箇所で使いたい場合はclose(sig)すれば良さそうです。

参考: http://qiita.com/ruiu/items/c58c343b589becc20937