ついでに Docker を使った環境構築をメモ。

開発環境

> docker --version
Docker version 19.03.12, build 48a66213fe

> docker-compose --version
docker-compose version 1.27.2, build 18f557f9

> go version
go version go1.16.3 linux/amd64

goroutine

goroutine (ゴルーチン)は、Goのランタイムに管理される軽量なスレッドです。
A Tour of Go

  • goroutine はスレッド
  • 関数呼び出しの前に go を付与すると、新しい goroutine が実行される
  • 新しい goroutine は呼び出し元の goroutine と同時に実行される

例1

A Tour of Go のコードを少し改良。

package main

import (
	"fmt"
	"time"
)

func say(s string) {
	for i := 0; i < 5; i++ {
		time.Sleep(100 * time.Millisecond)
		fmt.Println(i, s)
	}
}

func main() {
	go say("world")
	say("hello")
}

 

> go run main.go
0 world
0 hello
1 hello
1 world
2 world
2 hello
3 hello
3 world
4 world
4 hello

 

> go run main.go
0 world
0 hello
1 hello
1 world
2 world
2 hello
3 hello
3 world
4 hello
# 4 world が出力されない

 

何回か実行すると "4 world" が出力されないことがある。

"~ world" の出力は main 関数とは別の goroutine で実行されている。
main 関数の終了タイミングは main 関数と同じ goroutine で実行される "~ hello" の最後("4 hello")の処理が終わったタイミングなので、main 関数の goroutine の最後の処理が終わる前に他の goroutine の処理が終わっていなければそこで打ち切られてしまう。

 

package main

import (
	"fmt"
	"time"
)

func say(s string, num time.Duration) {
	for i := 0; i < 5; i++ {
		time.Sleep(100 * num * time.Millisecond)
		fmt.Println(i, s)
	}
}

func main() {
	go say("world", 100)
	say("hello", 1)
}

 

> go run main.go
0 hello
1 hello
2 hello
3 hello
4 hello

time.Sleep の重さを変えて実行したら main 関数とは別の goroutine で実行される処理が打ち切られてることが分かる。

例2

main 関数とは別の goroutine の処理が終わるまで待つ。

package main

import (
	"fmt"
	"sync"
	"time"
)

var wg = sync.WaitGroup{}

func say(s string) {
	defer wg.Done()
	for i := 0; i < 5; i++ {
		time.Sleep(100 * time.Millisecond)
		fmt.Println(i, s)
	}
}

func main() {
	wg.Add(2)
	go say("world")
	say("hello")
	wg.Wait()
}

 

> go run main.go
0 world
0 hello
1 hello
1 world
2 world
2 hello
3 hello
3 world
4 hello
4 world

sync パッケージの WaitGroup を使って goroutine を同期的に実行した。

 

sync.WaitGroup{} がグローバル変数なのが少し気になるので main に収まるようにクロージャを使って書き直した。

package main

import (
	"fmt"
	"sync"
	"time"
)

func say(s string, completion func()) {
	defer completion()
	for i := 0; i < 5; i++ {
		time.Sleep(100 * time.Millisecond)
		fmt.Println(i, s)
	}
}

func main() {
	wg := sync.WaitGroup{}
	wg.Add(2)
	go say("world", func() {
		wg.Done()
	})
	say("hello", func() {
		wg.Done()
	})

	wg.Wait()
}


Docker で Go の環境構築

Dockerfile

FROM golang:1.16-alpine
WORKDIR /go/src

COPY ./ ./
RUN go mod download

docker-compose.yml

version: '3.8'
services:
    app:
        build: .
        tty: true
        ports:
            - 8080:8080
        volumes:
            - .:/go/src

コマンド

# コンテナの構築・起動
# -d: detached コンテナをバックグラウンドで起動(このオプションが無いとターミナルが専有される)
> docker-compose up -d

# main.go の実行
> docker-compose exec app go run main.go

# コンテナに潜る
> docker-compose exec app /bin/sh  

 

ホスト上の相対パスをコンテナにマウントしているので、ホストで編集→コンテナで実行がホスト上で完結する。便利。

参考