Last active
December 18, 2015 19:09
-
-
Save Jxck/5831269 to your computer and use it in GitHub Desktop.
blog sample
advanced go concurrency pattern
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.DS_Store |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"fmt" | |
"io/ioutil" | |
"log" | |
"net/http" | |
"time" | |
) | |
// 指定された URL に GET リクエストし | |
// レスポンスの body とエラーを返す | |
func Get(url string) (body string, err error) { | |
res, err := http.Get(url) | |
if err != nil { | |
return | |
} | |
byteBody, err := ioutil.ReadAll(res.Body) | |
body = string(byteBody) | |
res.Body.Close() | |
if err != nil { | |
return | |
} | |
return | |
} | |
// Pooling 先の情報を保持する構造体 | |
type Pooling struct { | |
url string | |
result chan string | |
closing chan chan error | |
err error | |
} | |
type getResponse struct { | |
body string | |
err error | |
} | |
// 定期的な取得をするループ | |
func (p *Pooling) Loop() { | |
// Get が失敗したらこれに入れる | |
var err error | |
var buffer []string | |
const maxBuffer = 10 | |
var response chan getResponse | |
for { | |
var first string | |
var result chan string | |
if len(buffer) > 0 { | |
first = buffer[0] | |
result = p.result // ここを通らないと nil | |
} | |
var interval <-chan time.Time | |
if response == nil && len(buffer) < maxBuffer { | |
interval = time.After(1 * time.Second) // インターバル | |
} | |
select { | |
case finish := <-p.closing: | |
close(p.result) | |
finish <- err // 発生したエラーを返す | |
return | |
case <-interval: | |
response = make(chan getResponse, 1) | |
go func() { | |
body, err := Get(p.url) // err の方は単なる代入 | |
response <- getResponse{body, err} | |
}() | |
case res := <-response: | |
response = nil // 次のループのために nil に戻す | |
if res.err != nil { | |
// エラーがあったら、 3 秒間停止してすぐ再開 | |
time.Sleep(3 * time.Second) | |
fmt.Println("failed") | |
continue | |
} | |
// buffer に結果を追加 | |
buffer = append(buffer, res.body) | |
case result <- first: // result が nil だと実行されない | |
buffer = buffer[1:] | |
} | |
} | |
} | |
// loop を終了させる | |
func (p *Pooling) Close() error { | |
finish := make(chan error) | |
p.closing <- finish | |
return <-finish | |
} | |
func main() { | |
// 構造体を生成 | |
pooling := &Pooling{ | |
url: "http://www.google.com/sitemaps_webmasters.xml", | |
result: make(chan string), | |
closing: make(chan chan error), | |
} | |
// ループを開始 | |
go pooling.Loop() | |
// 終了処理 | |
time.AfterFunc(5*time.Second, func() { | |
err := pooling.Close() | |
if err != nil { | |
log.Fatal(err) | |
} | |
}) | |
// 結果の取り出しと表示 | |
for res := range pooling.result { | |
fmt.Println(res) | |
fmt.Println("=====================================") | |
} | |
} |
https://gist.github.com/Jxck/5831269#file-pooling-go-L81
dead lock を避けるために default があったほうがいい。
buffer = buffer[1:]
これは要素が 1 のとき ok ?
64 の channel の buffer が 1 な理由、 0 ではだめか?
0 の場合: 67 行目が block する
1 の場合: block しない。
つまり、 65 の goroutine のライフタイムに関わる?
67 で block したまま、finish になると、 return して
response を読み出す人がいなくなるから、 goroutine が終われない。
ゴミがのこるから?
1 の場合は、 goroutine は残らないけど、 return されると
response chan は(値が1つ残っているけど) 開放されるから ok。
鵜飼さんに聞いてみたい!
https://gist.github.com/Jxck/5831269#file-pooling-go-L49
この channel の定義を for の中ではなく外において、
毎回 nil にしてもいいのでは?
(変数確保のコストはどうなるのか?)
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
https://gist.github.com/Jxck/5831269#file-pooling-go-L31
closing chan chan<- error
の方がよい。