前書き
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
に代入することを意味する。i
が T
を保持していない場合、panic
を引き起こす。panic
は t, 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.Mutex
と Lock
、Unlock
の2つのメソッドで提供している。Lock
と Unlock
で囲むことで排他制御で実行するコードを定義できる。
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
}
最後に
正直、後半は理解できているかかなり怪しいです。間違っている箇所があっても全然おかしくないと思うので、指摘していただけると非常に助かります。