0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Goの基本

Last updated at Posted at 2021-12-26

前書き

Goを初めて学習する中で大事だと思った点をまとめました。ノート代わりの記事です。

主にA Tour of Goを参考にまとめました。

間違っている箇所がある場合は指摘していただけると幸いです。

Go言語とは

Googleが開発したプログラミング言語。また、オープンソースであり GitHub に公開されている。

特徴

  • 静的型付け

  • コンパイル言語

  • メモリ安全性

  • ガベージコレクション

  • 構造的型付け

  • 並列処理

以下からは実際のコードを交えて、基本的な文法を紹介していく。出力はコメントアウトで示している。

Import Packages

Goのプログラムはmainパッケージから開始されている。

複数のパッケージをインポートするときは、グループ化した方が美しい。

package main

import (
	"fmt"
	"math"
)

これ以降は基本的にパッケージのインポートを省略したコードを記述している。

Print, Println, Printf

  • fmt.Print() : 引数を文字列として出力

  • fmt.Println() : 引数の間にスペースを入れ、最後に改行文字 \n を出力

  • fmt.Printf() : %d (数値)や%s (文字列)等のフォーマットを指定して出力

import "fmt"
func main() {
    var v = 1
    var s = "string"
    fmt.Print("v = ", v, "\n")
    fmt.Println("v = ", v)
    fmt.Printf("v = %d, s = %s", v, s, "\n")
    // v = 1
    // v = 1
    // v = 1, s = string
}

fmt.Printf()のフォーマットに使用可能なもので、主に使われるものを以下に示す。詳しい記事。

verb
論理値(bool) %t
符号付き整数(int, int8,,,) %d
符号なし整数(uint, uint8,,,) %d
浮動小数点数(float64,,,) %g
複素数(complex128,,,) %g
文字列(string) %s
チャネル(shan) %p
デフォルト形式 %v
%そのものを出力 %%
文字 %c
型表示 $T
2進数 %b
ポインタ %p

Functions

変数名の後ろに型名を書く。返り値の方も指定する。

func add1(x int, y int) int {
    return x + y
}
// 引数が両方とも同じ型の時は省略できる
func add2(x, y int) int {
    return x + y
}

// 関数の呼び出し
func main() {
    fmt.Println(add1(1, 2))
    fmt.Println(add2(1, 2))
}

複数の戻り値の返し方。

func swap(x, y string) (string, string) {
    return y, x
}
// 戻り値の受け取り
func main() {
    a, b := swap("World!", "Hello")
}

戻り値に変数名を付けることができる。これをすることで、return ステートメントに何も書かなくてよくなる。これを naked return と呼ぶ。長い関数で naked return を使うと読みにくいので、短い関数のみで使うべき。

func split(sum int) (x, y int) {
    x = sum * 4 / 9
    y = sum -x
    return
}

変数宣言

var ステートメントで変数宣言を行う。また、宣言時に初期値を与えることができる。初期値を与えた場合、型を省略可能。しかし、関数内では、var ステートメントを用いずに := の代入文を使うことで型宣言が可能。また、変数宣言は、インポートステートメントと同様に、まとめて宣言可能。

var flag bool
var i, j int = 1, 2
var (
    name string = "chellwo"
    age  int    = 21
)
func main() {
    var c, python = true, "OK"
    java := "NO"
    fmt.Println(flag, i, j, c, python, java)
    // false, 1, 2, true, OK, NO 
    fmt.Println(name, age)
    // chellwo 21
}

変数に初期値を与えずに宣言すると、ゼロ値が与えられる。

func main() {
    var i int
    var f float64
    var b bool
    var s string
    fmt.Printf("%v %v %v %q\n", i, f, b, s)
    // 0 0 false ""
}

定数宣言

const ステートメントで定数宣言を行う。定数は、文字、文字列、ブール値、数値のみで使える。また、定数は := を使って宣言できない。

const Pi = 3.14
func main() {
    const World = "World"
    fmt.Println("Hello ", World)
    fmt.Println("pi = ", Pi)
    // Hello World
    // pi = 3.14
}

Go言語の基本型は以下の通り。

bool

string

int  int8  int16  int32  int64
uint uint8 uint16 uint32 uint64 uintptr

byte // uint8 の別名

rune // int32 の別名
     // Unicode のコードポイントを表す

float32 float64

complex64 complex128

int、uint、uintptr 型は32-bitのシステムでは32bitで、64-bitのシステムでは64bitです。特別な理由がない限りはintを使うべき。

型変換

変数v、型Tがあった場合、T(v)は、変数vをT型に変換する。

func main() {
    x, y := 1, 2
    f := float64(x) / float64(y)
    u := unit(f)
    fmt.Println(x, f, u)
    // 1 0.5 0
}

For

for 初期化ステートメント; 条件式; 後処理ステートメント; の形式で記述する。

func main() {
    sum := 0
    for i := 0; i < 10; i++ {
        sum += 1
    }
    fmt.Println(sum)
    // 45
}

初期化、後処理ステートメント、セミコロン、ループ条件の省略可能。ループ条件を省略した場合、無限ループとなる。

func main() {
    sum := 1
    // 初期化、後処理ステートメントの省略
    for ; sum < 10; {
        sum += sum
    }
    fmt.Println(sum)
    // 16

    // セミコロンの省略
    for sum < 1000 {
        sum += sum
    }
    fmt.Println(sum)
    // 1024

    // ループ条件の省略
    for {
    }
    // timeout
}

If and else

Goの場合、if ステートメントは、for のように条件の前に簡単なステートメントを記述できる。

func pow(x, n, lim float64) float64 {
	if v := math.Pow(x, n); v < lim {
		return v
	} else if v == lim {
		fmt.Printf("%g == %g\n", v, lim)
	} else {
		fmt.Printf("%g > %g\n", v, lim)
	}
	return lim
}

func main() {
	fmt.Println(
		pow(3, 2, 10),
		pow(3, 3, 20),
	)
    // 27 > 20
    // 9 20
}

Switch

switch 文。

func main() {
    fmt.Print("Go runs on ")
    switch os := runtime.GOOS; os {
        case "darwin":
            fmt.Println("OS X.")
        case "linux":
            fmt.Println("Linux.")
        default:
            fmt.Printf("%s.\n", os)
    }
}

条件のない switch 文を書くことができる。switch true と同義。

func main() {
    t := time.Now()
    switch {
        case t.Hour() < 12:
            fmt.Println("Good morning!")
        case t.Hour() < 17:
            fmt.Println("Good afternoon!")
        default:
            fmt.Println("Good evening!")
    }
}

Defer

defer ステートメントは、defer へ渡した関数の実行を呼び出し元の関数の終わり(return)まで遅延させる。defer へ渡した関数の引数はすぐに評価されるが、関数の実行は最後になる。

func main() {
    defer fmt.Println("World!!")
    fmt.Print("Hello ")
    // Hello World!!
}

defer へ渡した関数が複数ある場合、その関数はスタックされていく。したがって、実行される順番はLIFO(last-in-first-out)となる。

func main() {
    fmt.Println("counting")
    for i := 0; i < 3; i++ {
        defer fmt.Print(i, " ")
    }
    fmt.Println("done")
    // counting
    // done
    // 2 1 0
}

Pointer

Goではポインタを扱うことができる。ポインタはメモリアドレスのことを指す。変数 T のポインタは *T 型で、ゼロ値は nil である。

  • & オペレータは、そのオペランドへのポインタを示す。

  • * オペレータは、ポインタの指定先の変数を示す。

func main() {
    i, j := 40, 50

    p := &i         // iへのポインタ
	fmt.Println(*p) // ポインタを通して、iの読み込み
    // 40
	*p = 20         // ポインタを通して、iに書き込み
	fmt.Println(i) 
    // 20

	p = &j         // jへのポインタ
	*p = *p / 25   // ポインタを通して、割り算
	fmt.Println(j)
    // 2
}

Structs

struct (構造体)は、field フィールドの集まり。struct のフィールドは、. を用いてアクセスする。ポインタを通してアクセスすることも可能。

type Vertex struct {
    X int
    Y int
}
func main() {
    v := Vertex{1, 2}
    v.X = 4
    fmt.Println(v)
    // {4 2}

    p := &v
    p.X = 8
    fmt.Println(v)
    // {8 2}
}

Struct Literals

struct リテラルは、フィールドの値を列挙することで新しい struct の初期値を割り当てられる。Name: 構文を使うことで、フィールドの一部だけを列挙可能。

type Vertex struct {
    X, Y int
}
var (
    v1 = Vertex{}
    v2 = Vertex{X: 2}
    p = &Vertex{1, 2}
)
func main() {
    fmt.Println(v1, v2, p)
    // {0 0} {2 0} &{1 2}
}

配列

[n]T 型は、型 T のn個の変数の配列を表す。また、型 []T は型 T のスライスを表す。例えば、a[low:high] としたとき、インデックスが low <= index < high となるような要素を取り出す。上限と下限は省略することができる。(例: a[low:])

スライスは配列の参照のようなもののため、スライスの要素を変更すると、元の配列の要素にも変更が反映される。

func main() {
	var a [2]string
	a[0] = "Hello"
	a[1] = "World"
	fmt.Println(a[0], a[1])
    // Hello World
	fmt.Println(a)
    // [Hello World]

	primes := [6]int{2, 3, 5, 7, 11, 13}
	fmt.Println(primes)
    // [2 3 5 7 11 13]

    var s []int = primes[1:4]
    fmt.Println(s)
    // [3 5 7]
    fmt.Println(s[1:])
    // [5 7]

    s[0] = 100
    fmt.Println(primes)  // 参照元が変更されているか確認
    // [2 100 5 7 11 13]
}

スライスのリテラルは長さのない配列リテラルのようなものといえる。

func main() {
    s := []struct {
        i int
        b bool
    }{
        {2, true},
		{3, false},
		{5, true},
		{7, true},
		{11, false},
		{13, true},
	}
	fmt.Println(s)
    // [{2 true} {3 false} {5 true} {7 true} {11 false} {13 true}]
}

スライスは、長さ(length)と容量(capacity)を持つ。長さは含まれる要素数、容量はスライスに対して確保されているメモリ領域を示す。長さは len(s)、容量は cap(s) で得られる。容量に関して、この記事が分かりやすい。

func printSlice(s []int) {
	fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
func main() {
	s := []int{2, 3, 5, 7, 11, 13}
	printSlice(s)
    // len=6 cap=6 [2 3 5 7 11 13]

	s = s[:0]
	printSlice(s)
    // len=0 cap=6 []

	s = s[:4]
	printSlice(s)
    // len=4 cap=6 [2 3 5 7]

	s = s[2:]
	printSlice(s)
    // len=2 cap=4 [5 7]
}

スライスのゼロ値は nil であり、0の長さと容量を持つ。

func main() {
    var s []int
    fmt.Println(s, len(s), cap(s), s == nil)
    // [] 0 0 true
}

Make

make 関数によってゼロ化された配列を割り当て、その配列のスライスを得られる。make 関数は make(型, 長さ, 容量) として長さと容量を指定できる。

func printSlice(s string, x []int) {
	fmt.Printf("%s len=%d cap=%d %v\n",
		s, len(x), cap(x), x)
}
func main() {
	a := make([]int, 5)
	printSlice(a)
    // len=5 cap=5 [0 0 0 0 0]

	b := make([]int, 0, 5)
	printSlice(b)
    // len=0 cap=5 []

	c := b[:2]
	printSlice(c)
    // len=2 cap=5 [0 0]

	d := c[2:5]
	printSlice(d)
    // len=3 cap=3 [0 0 0]
}

他のスライスを含む任意の型を含めることが可能。

func main() {
    board := [][]string{
        []string{"1", "2", "3"},
        []string{"4", "5", "6"},
        []string{"7", "8", "9"},
    }
    for i := 0; i < len(board); i++ {
        fmt.Printf("%s\n", string.Join(board[i], " "))
    }
    // 1 2 3
    // 4 5 6
    // 7 8 9
}

Append

append(s []T, vs ...T) を使うことで、スライスに新しい要素を追加できる。s は追加元のスライス、その後ろの引数は追加する変数群を指している。変数群を追加する際に元の配列 s の容量が足りない場合、より大きいサイズの配列を割り当て直す。

func printSlice(s []int) {
	fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
func main() {
	var s []int
	printSlice(s)
    // len=0 cap=0 []

	s = append(s, 0)
	printSlice(s)
    // len=1 cap=1 [0]

	s = append(s, 1, 2, 3, 4)
	printSlice(s)
    // len=5 cap=6 [0 1 2 3 4]
}

Range

for ループで range が用いられるが、スライスを range で繰り返す場合、反復ごとに2つの変数を返している。1つ目はインデックスで、2つ目はインデックスの場所の要素である。

_ へ代入することで捨てることが可能。また、インデックスだけが必要な場合、2つ目の値を省略するとよい。

var prime = []int{2, 3, 5}

func main() {
    for i, v := range prime {
        fmt.Printf("index = %d, value = %d\n", i, v)
    }
    // index = 0, value = 2 
    // index = 1, value = 3 
    // index = 2, value = 5 
    
    pow := make([]int, 3)
	for i := range pow {
		pow[i] = 1 << uint(i) // == 2**i
	}
	for _, value := range pow {
		fmt.Printf("%d ", value)
	}
    // 1 2 4
}

Maps

map はキーと値を関連付ける。make 関数でマップを初期化可能。また、マップのゼロ値は nil であり、キーを追加することができない。もし、map に渡すトップレベルの方が単純な型名の場合、型名を省略可能。

type Vertex struct {
	height, weight float64
}

var m1 map[string]Vertex  // nilマップ
var m2 = map[string]Vertex{
    "chellwo": Vertex{
		169.5, 57.5,
	},
    "peko": {155.7, 45.2},  // 型名の省略
}

func main() {
	m1 = make(map[string]Vertex)  // マップの初期化(nilじゃなくなる)
	m1["chellwo"] = Vertex{
		169.5, 57.5,
	}
	fmt.Println(m1)
    // map[chellwo:{169.5 57.5}]
    fmt.Println(m1["chellwo"])
    // {169.5 57.5}
    fmt.Println(m2)
    // map[chellwo:{169.5 57.5} peko:{155.7, 45.2}]
}

map に対する操作。

func main() {
	m := make(map[string]int)

	// 要素の挿入
    m["key"] = 42
	fmt.Println(m["key"])
    // 42

	// 要素の更新
    m["key"] = 48
	fmt.Println(m["key"])
    // 48

	// 要素の削除
    delete(m, "key")
	fmt.Println(m["key"])
    // 0

	// 要素の取得と存在の確認
    v, ok := m["key"]
	fmt.Println(v, ok)
    // 0 false
}

Function Values

関数値は、関数の引数・戻り値として利用可能。

func compute(fn func(float64, float64) float64) float64 {
	return fn(3, 4)
}

func main() {
	hypot := func(x, y float64) float64 {
		return math.Sqrt(x*x + y*y)
	}
	fmt.Println(hypot(5, 12))
    // 13
	fmt.Println(compute(hypot))
    // 5
	fmt.Println(compute(math.Pow))
    // 81
}

Function closures

クロージャは関数値であり、それ自身の外部から変数を参照する。この関数は、参照された変数へバインドされており、変数にアクセスして変更することができる。

func adder() func(int) int {
	sum := 0
	return func(x int) int {
		sum += x
		return sum
	}
}
func main() {
	pos, neg := adder(), adder()
	for i := 0; i < 4; i++ {
		fmt.Println(
			pos(i),
			neg(-2*i),
		)
	}
    // 0 0
    // 1 -2
    // 3 -6
    // 6 -12
}

Methods

Goにクラスはないが、型にメソッドを定義できる。メソッドは、レシーバ引数を受け取り、レシーバは、func キーワードとメソッド名の間に自身の引数リストで表現する。

通常の関数として記述しても同じである。

レシーバが指す変数を変更する場合、レシーバ自身を変更することが多いため、ポインタレシーバを使う。変数レシーバの場合、変数のコピーを更新することになる。

ポインタレシーバの場合も関数として書くことができる。

type Vertex struct {
	X, Y float64
}
// 変数レシーバ
func (v Vertex) Abs() float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
// 通常の関数
func abs(v Vertex) float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
// ポインタレシーバ
func (v *Vertex) Scale(f float64) {
	v.X = v.X * f
	v.Y = v.Y * f
}
// 関数
func scale(v *Vertex, f float64) {
    v.X = v.X * f
	v.Y = v.Y * f
}
func main() {
	v := Vertex{3, 4}
	fmt.Println(v.Abs())
    // 5
    fmt.Println(abs(v))
    // 5
    v.Scale(10)
	fmt.Println(v.Abs())
    // 50
    scale(&v, 10)
    fmt.Println(abs(v))
    // 500
}

struct の型だけでなく、任意の型にもメソッドを宣言できる。

type MyFloat float64
func (f MyFloat) Abs() float64 {
	if f < 0 {
		return float64(-f)
	}
	return float64(f)
}
func main() {
	f := MyFloat(-math.Sqrt2)
	fmt.Println(f.Abs())
}

Pointer receiver

メソッドがポインタレシーバである場合、呼び出し時に、変数またはポインタのいずれかのレシーバとして受け取ることができる。また、ポインタレシーバだと、Goでは v.Scale(5) のステートメントを (&v).Scale(5) として解釈してくれる。

var v Vertex
v.Scale(5)    // OK
(&v).Scale(5) // OK
p := &v
p.Scale(10)   // OK

Value receiver

変数を引数とする関数の場合は、特定の方の変数を受け取る必要があるが、変数レシーバを引数とするメソッドの場合、変数とポインタのどちらでもレシーバとして受け取ることができる。

// Abs()は変数レシーバのメソッド
var v Vertex
fmt.Println(v.Abs())    // OK
p := &v
fmt.Println(p.Abs())    // OK
fmt.Println((*p).Abs()) // OK

以下の理由から、変数レシーバではなく、ポインタレシーバを使うことが推奨されている。

  • メソッドがレシーバが指す先の変数を変更する。

  • メソッドの呼び出しごとに変数のコピーを避けることができる。

Interfaces

interface (インターフェース)型は、メソッドのシグネチャの集まりを定義している。そのメソッドの集まりを実装した値を、interface 型の変数へ持たせることができる。

type Abser interface {
	Abs() float64
}

func main() {
	var a Abser
	f := MyFloat(-math.Sqrt2)
	v := Vertex{3, 4}

	a = f
    fmt.Println(a.Abs()) // 変数レシーバのAbsメソッドが実行される
	a = &v
	fmt.Println(a.Abs()) // ポインタレシーバのAbsメソッドが実行される
}

type MyFloat float64

func (f MyFloat) Abs() float64 {
	if f < 0 {
		return float64(-f)
	}
	return float64(f)
}

type Vertex struct {
	X, Y float64
}

func (v *Vertex) Abs() float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

インターフェースの値は、値と具体的な型のタプル (value, type) のように考えられる。インターフェース自体の中にある具体的な値が nil の場合、メソッドは nil をレシーバーとして呼び出す。Goでは、nil をレシーバーとして呼び出されても適切に処理するメソッドを記述するのが一般的。

type I interface {
	M()
}

type T struct {
	S string
}

func (t *T) M() {
	if t == nil {
		fmt.Println("<nil>")
		return
	}
	fmt.Println(t.S)
}

func main() {
	var i I

	var t *T
	i = t
	describe(i)
    // (<nil>, *main.T)
	i.M()
    // <nil>

	i = &T{"hello"}
	describe(i)
    // (&{hello}, *main.T)
	i.M()
    // hello
}

func describe(i I) {
	fmt.Printf("(%v, %T)\n", i, i)
}

nilインターフェースは、値も型も保持していない。メソッドを示す型がインターフェースのタプル内に存在しないため、 nil インターフェースのメソッドを呼び出すと、ランタイムエラーとなる。

type I interface {
	M()
}

func main() {
	var i I
	describe(i)
    // (<nil>, <nil>)
	i.M()
    // error
}

func describe(i I) {
	fmt.Printf("(%v, %T)\n", i, i)
}

ゼロ個のメソッドを指定されたインターフェースは、空のインターフェースと呼ばれ、任意の型の値を保持できる。

func main() {
	var i interface{}
	describe(i)
    // (<nil>, <nil>)
	i = 42
	describe(i)
    // (42, int)
	i = "hello"
	describe(i)
    // (hello, string)
}

func describe(i interface{}) {
	fmt.Printf("(%v, %T)\n", i, i)
}

Type assertions

型アサーションは、インターフェースの値のもとになる具体的な値を利用する手段を提供する。t := i.(T) は、インターフェースの値 i が具体的な型 T を保持し、もとになるの値 T を変数 t に代入することを意味する。iT を保持していない場合、panic を引き起こす。panict, ok := i.(T) とすることで防ぐことができ、ok にはアサーションが成功したかどうかのブール値が格納される。

func main() {
	var i interface{} = "hello"

	s := i.(string)
	fmt.Println(s)
    // hello
	s, ok := i.(string)
	fmt.Println(s, ok)
    // hello true
	f, ok := i.(float64)
	fmt.Println(f, ok)
    // 0 false
	f = i.(float64)
	fmt.Println(f)
    // panic
}

Type switches

switch は通常の switch 文とは異なり、case で型を指定し、指定されたインターフェースが保持する値の型と比較される。

func do(i interface{}) {
	switch v := i.(type) {
	case int:
		fmt.Printf("Twice %v is %v\n", v, v*2)
	case string:
		fmt.Printf("%q is %v bytes long\n", v, len(v))
	default:
		fmt.Printf("I don't know about type %T!\n", v)
	}
}

func main() {
	do(21)
    // Twice 21 is 42
	do("hello")
    // "hello" is 5 bytes long
	do(true)
    // I don't know about type bool!
}

Stringers

Stringer インターフェースでは、string として表現することができる型である。

type Stringer interface {
    String() string
}
type Person struct {
	Name string
	Age  int
}

func (p Person) String() string {
	return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
}

func main() {
	a := Person{"chellwo", 21}
	z := Person{"peko", 20}
	fmt.Println(a, z)
    // chellwo (21 years) peko (20 years)
}

Errors

Goでは、エラーの状態を error 値で返す。error 型は組込みのインターフェースである。

type error interface {
    Error() string
}

関数は error 変数を返すため、呼び出し元はエラーが nil かどうかを確認することでエラーハンドリングを行う。

type MyError struct {
	When time.Time
	What string
}

func (e *MyError) Error() string {
	return fmt.Sprintf("at %v, %s",
		e.When, e.What)
}

func run() error {
	return &MyError{
		time.Now(),
		"it didn't work",
	}
}

func main() {
	if err := run(); err != nil {
		fmt.Println(err)
        // at 2009-11-10 23:00:00 +0000 UTC m=+0.000000001, it didn't work
	}
}

Readers

io.Reader を用いることでデータストリームを読み込める。io.Reader インターフェースは Read メソッドを持つ。

func (T) Read(b []byte) (n int, err error)

Read は、データを与えられたバイトスライスへ格納し、バイトのサイズとエラーの値を返す。ストリームの終端で io.EOF エラーを返す。

func main() {
	r := strings.NewReader("Hello, Reader!")

	b := make([]byte, 8)
	for {
		n, err := r.Read(b)
		fmt.Printf("n = %v err = %v b = %v\n", n, err, b)
		fmt.Printf("b[:n] = %q\n", b[:n])
		if err == io.EOF {
			break
		}
	}
    // n = 8 err = <nil> b = [72 101 108 108 111 44 32 82]
    // b[:n] = "Hello, R"
    // n = 6 err = <nil> b = [101 97 100 101 114 33 32 82]
    // b[:n] = "eader!"
    // n = 0 err = EOF b = [101 97 100 101 114 33 32 82]
    // b[:n] = ""
}

Images

image パッケージは、以下の Image インターフェースを定義している。詳細はこのドキュメント

package image

type Image interface {
    ColorModel() color.Model
    Bounds() Rectangle
    At(x, y int) color.Color
}
func main() {
	m := image.NewRGBA(image.Rect(0, 0, 100, 100))
	fmt.Println(m.Bounds())
    // (0,0)-(100,100)
	fmt.Println(m.At(0, 0).RGBA())
    // 0 0 0 0
}

Gorutines

goroutine は、Goのランタイムに管理される軽量なスレッドである。これによって並列実行が可能になる。

func say(s string) {
	for i := 0; i < 3; i++ {
		time.Sleep(100 * time.Millisecond) // これを入れないと並列実行できない
		fmt.Println(s)
	}
}

func main() {
	go say("world")
	say("hello")
    // 出力例
    // world
    // hello
    // hello
    // world
    // world
    // hello
}

Channels

チャネル型ではチャネルオペレータの <- を用いることで値の送受信ができる。

ch <- v    // v をチャネル ch へ送信する
v := <-ch  // ch から受信した変数を v へ割り当てる

片方が準備できるまで送受信はブロックされるので、明確なロックや条件変数がなくても、goroutine の同期を可能とする。

func sum(s []int, c chan int) {
	sum := 0
	for _, v := range s {
		sum += v
	}
	c <- sum // cに送信
}

func main() {
	s := []int{7, 2, 8, -9, 4, 0}

	c := make(chan int) // チャネルの生成
	go sum(s[:len(s)/2], c)
	go sum(s[len(s)/2:], c)
	x, y := <-c, <-c // cから受信した変数の割り当て

	fmt.Println(x, y, x+y)
    // -5 17 12
}

チャネルは、バッファとして使うことができ、make の2つ目の引数にバッファの長さを与えることで初期化できる。バッファが詰まった時は、チャネルへの送信をブロックし、バッファが空の時には、チャネルの送信をブロックする。

func main() {
	ch := make(chan int, 2)
	ch <- 1
	ch <- 2
	fmt.Println(<-ch)
    // 1
	fmt.Println(<-ch)
    // 2
	ch <- 3
	fmt.Println(<-ch)
    // 3
}

これ以上送信する値がないことを示したいとき、チャネルを close するとよい。受信の式に2つ目のパラメータを割り当てて、そのチャネルが close されているかどうかを確認できる。

注意
送り手のチャネルだけを closeする。受け手は closeしてはいけない。close 下チャネルへ送信すると、panic する。

func fibonacci(n int, c chan int) {
	x, y := 0, 1
	for i := 0; i < n; i++ {
		c <- x
		x, y = y, x+y
	}
	close(c)
}

func main() {
	c := make(chan int, 4)
	go fibonacci(cap(c), c)
	for i := range c {
		fmt.Print(i, " ")
	}
    // 0 1 1 2
	_, ok := <- c
	fmt.Println("\n", ok) // true -> open, false -> close 
    // false
}

Select

select ステートメントは、goroutine を複数の通信操作で待たせる。具体的には、複数ある case のいずれかが準備できるようになるまでブロックし、準備ができた case を実行する。もし、複数の case の準備ができている場合、case はランダムに実行される。

func fibonacci(c, quit chan int) {
	x, y := 0, 1
	for {
		select {
		case c <- x:
			x, y = y, x+y
		case <-quit:
			fmt.Println("\nquit")
			return
		}
	}
}

func main() {
	c := make(chan int)
	quit := make(chan int)
	go func() {
		for i := 0; i < 4; i++ {
			fmt.Print(<-c, " ")
		}
		quit <- 0
	}()
	fibonacci(c, quit)
    // 0 1 1 2
    // quit
}

どの case も準備できていないときは、select の中の default が実行される。

func main() {
	tick := time.Tick(100 * time.Millisecond)
	boom := time.After(500 * time.Millisecond)
	for {
		select {
		case <-tick:
			fmt.Println("tick.")
		case <-boom:
			fmt.Println("BOOM!")
			return
		default:
			fmt.Println("    .")
			time.Sleep(50 * time.Millisecond)
		}
	}
    //     .
    //     .
    // tick.
    //     .
    //     .
    // tick.
    //     .
    //     .
    // BOOM!
}

sync.Mutex

通信が必要ない場合で、コンフリクトを避けるために、一度に1つの goroutine だけが変数にアクセスできるようにしたいとき、排他制御を用いる。このデータ構造は、一般的に mutex と呼ばれる。

Goの標準ライブラリは、排他制御を sync.MutexLockUnlock の2つのメソッドで提供している。LockUnlock で囲むことで排他制御で実行するコードを定義できる。

type SafeCounter struct {
	mu sync.Mutex
	v  map[string]int
}

func (c *SafeCounter) Inc(key string) {
	c.mu.Lock()
	c.v[key]++
	c.mu.Unlock()
}

func (c *SafeCounter) Value(key string) int {
	c.mu.Lock()
	defer c.mu.Unlock()
	return c.v[key]
}

func main() {
	c := SafeCounter{v: make(map[string]int)}
	for i := 0; i < 1000; i++ {
		go c.Inc("somekey")
	}

	time.Sleep(time.Second)
	fmt.Println(c.Value("somekey"))
    // 1000
}

最後に

正直、後半は理解できているかかなり怪しいです。間違っている箇所があっても全然おかしくないと思うので、指摘していただけると非常に助かります。

0
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?