Skip to content

Instantly share code, notes, and snippets.

@Jxck
Last active December 18, 2015 19:09
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Jxck/5831269 to your computer and use it in GitHub Desktop.
Save Jxck/5831269 to your computer and use it in GitHub Desktop.
blog sample advanced go concurrency pattern
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("=====================================")
}
}
@Jxck
Copy link
Author

Jxck commented Jul 3, 2013

https://gist.github.com/Jxck/5831269#file-pooling-go-L31
closing chan chan<- error
の方がよい。

@Jxck
Copy link
Author

Jxck commented Jul 3, 2013

https://gist.github.com/Jxck/5831269#file-pooling-go-L81
dead lock を避けるために default があったほうがいい。

@Jxck
Copy link
Author

Jxck commented Jul 3, 2013

buffer = buffer[1:]
これは要素が 1 のとき ok ?

@Jxck
Copy link
Author

Jxck commented Jul 3, 2013

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。

鵜飼さんに聞いてみたい!

@Jxck
Copy link
Author

Jxck commented Jul 3, 2013

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