Skip to content

Instantly share code, notes, and snippets.

@hagino3000
Created March 30, 2016 05:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hagino3000/2ed5ff99d81e6899b02323cf17065055 to your computer and use it in GitHub Desktop.
Save hagino3000/2ed5ff99d81e6899b02323cf17065055 to your computer and use it in GitHub Desktop.
go.pl Chapter 8

The Go Programming Language chapter 8

8. Goroutines and Channels

並行プログラミング、重要だよね。モバイルアプリはネットワーク通信の間アニメーションしたりする。並行性はI/Oのレイテンシを隠すために使われモダンなコンピューターのプロセッサを消費する。

Goは二つのスタイルの並行プログラミングを可能にしている。この章ではCommunicating Sequential Processes(CSP)をサポートする goroutinechannels を紹介する。

9章ではより伝統的なモデルの共有メモリマルチスレッドの一面をカバーしている。9章はさらにこの章で掘り下げなかった並行プログラミングの落し穴にも触れる。

Goの並行性のサポートはそれの強みとはいえ、内在的に逐次プログラムと比較して並行プログラムは難しいし。逐次プログラムでの直感は頼りにならなかったりする。こういうのが初めてなら、この章と次の章に時間かけてみて。

8.1 Goroutines

Goにおいて並行実行アクティビティはgoroutineと呼ぶ。 二つの関数を持つプログラムを考えてみよう、一つは計算をし、もう一つは何かに書き出す。互いに呼び出しあわないという仮定の元で。 逐次プログラムでは一つの関数が終ってから次の関数を呼ぶ。並行プログラムでは二つかそれ以上の goroutineを同時に有効化する。

goroutineはスレッドに近い。スレッドとgoroutineの差は定量的な物で性質では無い、9.8章で説明する。

プログラム起動時、goroiutineは main 関数を呼ぶ1個だけ存在する、これを main goroutine と呼ぶ。新しいgoroutineはgo宣言によって生成される。

f() // fが終るまで待つ
go f() // 新規goroutineでfを実行、待たない

次のサンプルは、フィボナッチの計算をしている間、ヴィジュアルエフェクトを回す。

ch8/spinner

main関数がreturnする時、全てのgoroutineはterminateされプログラムは終了する。(good) goroutineを他から止める方法は無いが、コミュニケートして停止リクエストを送る方法は後程。

8.2 Example: Concurrent Clock Server

ネットワーキングは並行性を使う自然なドメインだ、なぜならサーバーは一度に多くのクライアントとの接続をハンドルするし、それぞれの接続は独立している。この章では net パッケージを紹介する。TCP, UDP, Unixドメインソケットが使えるやつ。

最初のサンプルは逐次的な時計サーバー。

gopl.io/ch8/clock1

  • net.Listenで接続を待ちうける
  • listener.Acceptでクライアントの接続要求を受けつけ
  • net.Connに現在時刻をwriteStringしつづける (無限ループ)

このサンプルは、同時に一つのクライアント接続しか受けつけない

$ nc localhost 8000
08:58:40
08:58:41
08:58:42
08:58:43
08:58:44
08:58:45
08:58:46

telnetでもなんでもいいけど、localhost:8000に接続すると時刻が流れてくる。

次のコードはRead Only TCPクライアント。

gopl.io/ch8/netcat1

net.Dialでクライアントが接続する。

clock1は同時に1つの接続しかハンドルできなかったが、これに僅かな変更を加えるだけで複数の接続を扱えるようになる。

// before
// handleConn(conn)

// after
go handleConn(conn)

Exercise 8.1

clock2を改造してこんな感じに使う物を作れ

$ TZ=US/Eastern    ./clock2 -port 8010 &
$ TZ=Asia/Tokyo    ./clock2 -port 8020 &
$ TZ=Europe/London ./clock2 -port 8030 &
$ clockwall NewYork=localhost:8010 London=localhost:8020 Tokyo=localhost:8030

サーバー側

var port = flag.Int("port", 8000, "Listen port number")

...

	flag.Parse()
	listener, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", *port))

クライアント

package main

import (
	"flag"
	"io"
	"log"
	"net"
	"os"
	"fmt"
	"strings"
	"time"
)

type locationTimeWriter struct {
	w io.Writer
	locationName string
}

func (w *locationTimeWriter) Write(data []byte) (int, error) {
	return w.w.Write(data)
	//return w.w.Write([]byte(w.locationName + string(data)))
}


func main() {
	flag.Parse()
	fmt.Println(flag.Args())
	for _, remote := range flag.Args() {
		var name_addr = strings.Split(remote, "=")
		var name = name_addr[0]
		var addr = name_addr[1]
		go handleConn(name, addr)
	}
	for {
		time.Sleep(100 * time.Millisecond)
	}
}

func handleConn(name string, address string) {
	conn, err := net.Dial("tcp", address)
	fmt.Printf("Connected [%s] %s\r\n", name, address)
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()
	var out = &locationTimeWriter{w: os.Stdout, locationName: name}
	mustCopy(out, conn)
	fmt.Println("OK")
}

func mustCopy(dst io.Writer, src io.Reader) {
	if _, err := io.Copy(dst, src); err != nil {
		log.Fatal(err)
	}
}

8.3 Example: Concurrent Echo Server

サーバー側

  • Acceptした接続毎に入力毎のechoを返す (goroutineで並列化)

クライアント側

  • サーバーからのレスポンスをStdoutに書く (goroutineで並行して動く)
  • 標準入力をソケットに書く
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment