Open4

学習ログ|Go

arainuarainu

返り値のルール

・返り値がある場合は、原則返り値の型指定は必要
・複数の返り値があり場合は、型指定もそれぞれに対して型指定が必要
・名前付き返り値の場合は、複数の返り値がある場合もreturnの記述のみで返却される

変数のルール

・小文字で宣言した変数=private:同一パッケージ内では呼び出せるが、別のパッケージからは呼び出せない
・大文字で宣言した変数=public:別のパッケージからも呼び出せる
・型を明示した変数宣言、複数の同じ型の変数であればカンマ区切りで宣言初期化できる
・短縮変数宣言の場合、関数外で定義したものはスコープ外となる。

Zero Values

・Goの特徴として、すべての型にゼロ値が定義されている

for文の初期化変数宣言

for構文

for 初期化; 条件; 更新 {
    // 処理
}

初期化部分には「短縮変数宣言のみが使用可能」。通常の型指定やvarを使用すると構文エラーとなる。

arainuarainu

スライス

スライス ≒ 配列への参照
👉スライス自体にデータはない
元の配列の特定の部分を見るための「窓」のようなもの

  • スライスの要素を変更すると、元となる配列の対応する要素が変更される。
  • 元となる配列を共有している他のスライスも同様に変更が反映される、

スライスの範囲指定

配列名[start:end]

  • 指定方法は「半開指定」
    • startのインデックスから初めて、endの手前のインデックスまでを含む
arainuarainu

goroutine|ゴルーチン

🤔そもそも「並行処理」とは?
同時に進んでいる「ように扱う」こと

並行処理:処理主体が一つで、複数の処理を切り替えて進める。
並列処理:複数のコアやプロセッサを使い、複数の処理を「物理的に同時」に進行させる。

プロセス|実行中のアプリ本体
スレッド|プログラムの中で処理の流れを担当する最小単位

スレッドとgoroutineは「正社員」と「バイトスタッフ」のような関係(ざっくり)

順次処理
package main

import (
	"fmt"
	"time"
)

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

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

func main() {
	goroutine("Goroutine!")
	normal("Go!")
}
% go run lesson.go
Goroutine!
Goroutine!
Goroutine!
Goroutine!
Goroutine!
Go!
Go!
Go!
Go!
Go!
ゴルーチン
// 呼び出す関数の前に go を付与
func main() {
	go goroutine("Goroutine!")
	normal("Go!")
}
Go!
Goroutine!
Goroutine!
Go!
Go!
Goroutine!
Go!
Goroutine!
Goroutine!
Go!
ゴルーチン(並行処理)の処理が終わらなくても処理終了してしまう
package main

import (
	"fmt"
	// "time"
)

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

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

func main() {
	go goroutine("Goroutine!")
	normal("Go!")
}
Go!
Go!
Go!
Go!
Go!

・スリープにより実行時間を確保
・ゴルーチンの実行順序は保証されない

package main

import (
	"fmt"
	"time"
)

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

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

func main() {
	go goroutine("Goroutine!")
	normal("Go!")
	time.Sleep(100 * time.Millisecond)
}
Go!
Go!
Go!
Go!
Go!
Goroutine!
Goroutine!
Goroutine!
Goroutine!
Goroutine!
sync.WaitGroupで並行処理を待機させる

※ver1.25では、Add()・Done()を明示的に記述しなくても良くなっているとのこと(詳細は割愛)

package main

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

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

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

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

func main() {
	var wg sync.WaitGroup
	wg.Add(2)
	go goroutine1("Goroutine1!", &wg)
	go goroutine2("Goroutine2!", &wg)
	normal("Go!")
	wg.Wait()
}
Goroutine1!
Goroutine2!
Go!
Goroutine1!
Go!
Goroutine2!
Goroutine2!
Goroutine1!
Go!
Go!
Goroutine2!
Goroutine1!
Goroutine1!
Go!
Goroutine2!

推奨|deferを使って先に記述しておく

package main

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

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

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

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

func main() {
	var wg sync.WaitGroup
	wg.Add(2)
	go goroutine1("Goroutine1!", &wg)
	go goroutine2("Goroutine2!", &wg)
	normal("Go!")
	wg.Wait()
}
まとめ
  • 呼び出す関数の前にgoをつけるだけで並行処理が実現できるのがゴルーチン
  • sync.WaitGroupを使用しないと、実装によっては並行処理実行前にプログラムが終了してしまう
  • deferを使用して、Done()処理の記述を忘れないようにする