Skip to content

Instantly share code, notes, and snippets.

@evalphobia
Last active December 5, 2023 03:52
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 evalphobia/0bdd7926ccbef0249f9a to your computer and use it in GitHub Desktop.
Save evalphobia/0bdd7926ccbef0249f9a to your computer and use it in GitHub Desktop.

6 Concurrency 並行処理

  • ゴルーチンを使ったコード
  • レースコンディションの検知と修正
  • チャネルによるデータ共有

・スケジューラーがどのゴルーチンがどのプロセッサで動いているか管理している
・CSP message passing model(共有データをロックするのではなくチャネルでの受け渡し)

6.1 Concurrency vs parallelism 並行 vs 並列

スレッドと論理プロセッサと実行キュー

ブロック処理が入ると、スレッド&ゴルーチンは論理プロセッサから切り離されブロックが終わる(システムコールが返ってくる)のを待つ。
この間は論理プロセッサで実行しているスレッドは存在せず、
スケジューラーが実行キューにある他のゴルーチンを選択する。
ブロックが終了すると実行キューに再度入る
(ネットワークI/Oの場合は専用動作あり)

スレッド数の最大値はデフォルト1万(debug.SetMaxThreadsで変更可)
https://golang.org/pkg/runtime/debug/#SetMaxThreads

論理プロセッサが複数あれば、複数のスレッドで同時にゴルーチンが実行される

6.2 goroutine ゴルーチン

大文字と小文字を並行に出力する例
https://play.golang.org/p/JpdnN0EIar

GOMAXPROCS でスケジューラーが論理プロセッサを何個使うか設定
関数の前に go をつけて実行すると新しいゴルーチンが作られて実行される
(実行キューにはLIFOで入る??)

sync.WaitGroup は数値カウントのセマフォで、
Waitを使うと数値が0より上の時、処理をブロックしてくれる。
https://golang.org/pkg/sync/#WaitGroup

ゴルーチン内では必ず defer を使うこと!

スケジューラーによって実行中のゴルーチンが切り替わる例
https://play.golang.org/p/6fyRhM87Lm

論理プロセッサが複数ある場合に並列に動く例
https://play.golang.org/p/E2ftTJnSvt

6.3 race condition レースコンディション

レースコンディション = 複数のゴルーチンが共有しているリソースに対して、非同期に同時に読み書きする時
共有リソースに対しては、通常は1つのゴルーチンのみが処理する必要がある

ローカル変数がコピーされずに毎回2で上書きされる例
https://play.golang.org/p/eUE0O_PRfg

(runtime.Goschedで強制的にゴルーチンの切り替えをしている)

-race オプション付きで 実行/ビルド するとレースコンディションを検知できる

6.4 Locking shared resources ロック

6.4.1 atomic

atomic.AddInt64()
https://play.golang.org/p/AXE80vvrU2

atomic.LoadInt64() & atomic.StoreInt64()
https://play.golang.org/p/H3hDtoY-t7

6.4.2 mutex

sync.Mutex
https://play.golang.org/p/Dd0ocqMTVx

6.5 Channels チャネル

atomicやmutexのようなロックをしなくても、競合を解決できる方法がチャネル
チャネルにしたい型を指定してmakeで作成する

つかいかた

// 作成: make( chan [型] , [数] )
ch1 := make(chan int)
ch2 := make(chan int, 10)


// 送信: [チャネル] <- [値]
ch1 <- 1

// 受信: <-[チャネル]
<-ch1

6.5.1 バッファなしチャネル

送信・受信側、双方とも相手を待つ(ゴルーチンの実行をロックする)挙動になる

バッファなしチャネルでテニスゲームを模した(ボールの受け渡しをチャネルで行う)例 https://play.golang.org/p/kXltlBxiuw

リレー走を模した例 https://play.golang.org/p/I0wqElXclW

6.5.2 バッファありチャネル

送信側はバッファがいっぱいにならない限りブロックしない 受信側はバッファが空にならない限りブロックしない

バッファありチャネルを使ったワーカーの例 https://play.golang.org/p/23xdrQcHsW

6.6 まとめ

  • 各ゴルーチンを独立して実行させることで並行処理を実現している
  • 関数の呼び出し時にgo をつけるだけでゴルーチンになる
  • ゴルーチンは単一のOSスレッドが使用している論理コアで動作し、キューを実行する(?)
  • レースコンディションは複数のゴルーチンが同一のリソースにアクセスする時に起こる
  • レースコンディションを防ぐには、atomicやmutexを使う
  • チャネルは2つのゴルーチン間で安全にデータを共有するためのGoに組み込まれた方法
  • バッファなしチャネルではデータの交換が保証されるが、バッファありチャネルでは保証されない。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment