GoroutineからChannel:GoのCSPモデルを理解する
Lukas Schneider
DevOps Engineer · Leapcell

Preface
GoのCSP並行処理モデルの実装は、主に2つのコンポーネントで構成されています。1つはGoroutine、もう1つはチャネルです。この記事では、それらの基本的な使用法と注意点を紹介します。
Goroutine
Goroutineは、Goアプリケーションの基本的な実行単位です。軽量なユーザレベルのスレッドであり、その基盤となる並行処理の実装はコルーチンに基づいています。周知のように、コルーチンはユーザモードで実行されるユーザスレッドです。したがって、GoroutineもGoランタイムによってスケジュールされます。
基本的な使用法
構文:
go
+ 関数/メソッド
go
キーワードの後に関数/メソッドを続けることで、Goroutineを作成できます。
コード例:
import ( "fmt" "time" ) func printGo() { fmt.Println("名前付き関数") } type G struct { } func (g G) g() { fmt.Println("メソッド") } func main() { // 名前付き関数からgoroutineを作成 go printGo() // メソッドからgoroutineを作成 g := G{} go g.g() // 匿名関数からgoroutineを作成 go func() { fmt.Println("匿名関数") }() // クロージャからgoroutineを作成 i := 0 go func() { i++ fmt.Println("クロージャ") }() time.Sleep(time.Second) // 作成されたgoroutineが実行される前にメインgoroutineが終了するのを防ぐために、1秒間スリープします }
実行結果:
名前付き関数
メソッド
匿名関数
複数のGoroutineが存在する場合、それらの実行順序は固定されていません。したがって、出力結果は毎回異なります。
コードからわかるように、go
キーワードを使用することで、名前付き関数やメソッド、匿名関数やクロージャに基づいてGoroutineを作成できます。
では、Goroutineはどのように終了するのでしょうか?通常、Goroutineの関数が実行を終えるか、戻り値があればそれを返すとすぐに終了します。Goroutine内の関数またはメソッドが戻り値を持つ場合、Goroutineが終了するときに無視されます。
channel
チャネルは、Goの並行処理モデルで重要な役割を果たします。Goroutine間の通信や、Goroutine間の同期に使用できます。
channelの基本的な操作
チャネルは複合データ型であり、宣言するときに、格納する要素の型を指定する必要があります。
宣言構文:
var ch chan string
上記のコードは、要素の型がstring
のチャネルを宣言しています。つまり、string
値のみを格納できます。チャネルは参照型であり、データが書き込まれる前に初期化する必要があります。初期化にはmake
を使用します。
import ( "fmt" ) func main() { var ch chan string ch = make(chan string, 1) // チャネルのアドレスを出力 fmt.Println(ch) // "Go"をchに送信 ch <- "Go" // chからデータを受信 s := <-ch fmt.Println(s) // Go }
ch <- xxx
を使用してチャネル変数ch
にデータを送信し、x := <-ch
を使用してデータを受信できます。
バッファ付きチャネルとバッファなしチャネル
チャネルを初期化するときに容量を指定しない場合、バッファなしチャネルが作成されます。
ch := make(chan string)
バッファなしチャネルでは、送受信操作は同期的です。送信操作を実行した後、対応するGoroutineは、別のGoroutineが受信操作を実行するまでブロックされます。逆もまた同様です。送受信操作が同じGoroutineに配置されている場合はどうなるでしょうか?次のコードを見てみましょう。
import ( "fmt" ) func main() { ch := make(chan int) // データ送信 ch <- 1 // fatal error: all goroutines are asleep - deadlock! // データ受信 n := <-ch fmt.Println(n) }
プログラムを実行すると、ch <-
で致命的なエラーが発生し、すべてのGoroutineがスリープ状態である、つまりデッドロックが発生したと表示されます。これを回避するには、送受信操作を異なるGoroutineに配置する必要があります。
import ( "fmt" ) func main() { ch := make(chan int) go func() { // データ送信 ch <- 1 }() // データ受信 n := <-ch fmt.Println(n) // 1 }
上記の例から、バッファなしチャネルの場合、送受信操作は2つの異なるGoroutineで実行する必要があり、そうでない場合はデッドロックが発生することがわかります。
容量を指定すると、バッファ付きチャネルが作成されます。
ch := make(chan string, 5)
バッファ付きチャネルは、バッファなしチャネルとは異なります。送信操作を実行するとき、チャネルのバッファがいっぱいになっていない限り、Goroutineは中断されません。バッファがいっぱいになった場合にのみ、チャネルへの送信によってGoroutineが中断されます。コード例:
func main() { ch := make(chan int, 1) // データ送信 ch <- 1 ch <- 2 // fatal error: all goroutines are asleep - deadlock! }
送信専用チャネルと受信専用チャネルの宣言
送受信両方が可能なチャネル
ch := make(chan int, 1)
上記のコードでは、送受信操作の両方を実行できるチャネル変数が得られます。
受信専用チャネル
ch := make(<-chan int, 1)
上記のコードでは、受信操作のみを実行できるチャネル変数が得られます。
送信専用チャネル
ch := make(chan<- int, 1)
上記のコードでは、送信操作のみを実行できるチャネル変数が得られます。
通常、送信専用および受信専用のチャネル型は、関数のパラメータ型または戻り値として使用されます。
func send(ch chan<- int) { ch <- 1 } func recv(ch <-chan int) { <-ch }
チャネルを閉じる
組み込み関数close(c chan<- Type)
を使用してチャネルを閉じることができます。
送信側でチャネルを閉じる チャネルを閉じた後、それに対して送信操作を実行することはできません。そうしないと、チャネルがすでに閉じられていることを示すパニックが発生します。
func main() { ch := make(chan int, 5) ch <- 1 close(ch) ch <- 2 // panic: send on closed channel }
チャネルを閉じた後でも、それに対して受信操作を実行できます。チャネルにバッファがある場合、バッファされたデータが最初に読み取られます。バッファが空の場合、取得される値はチャネルの要素型のゼロ値になります。
import "fmt" func main() { ch := make(chan int, 5) ch <- 1 close(ch) fmt.Println(<-ch) // 1 n, ok := <-ch fmt.Println(n) // 0 fmt.Println(ok) // false }
for-range
でチャネルをトラバースするとき、反復中にチャネルが閉じられると、for-range
ループが終了します。
Summary
この記事では、まずGoroutineの作成方法と終了条件を紹介しました。
次に、バッファ付きとバッファなしの両方のチャネル変数の作成方法を説明しました。バッファなしチャネルの場合、送受信操作は2つの異なるGoroutineで実行する必要があり、そうでない場合はエラーが発生することに注意してください。
次に、送信専用および受信専用のチャネル型を定義する方法を説明しました。通常、これらの型は、関数のパラメータ型または戻り値として使用されます。
最後に、チャネルを閉じる方法と、閉じた後のいくつかの注意点について説明しました。
Goプロジェクトのホスティングに最適なLeapcellをご紹介します。
Leapcellは、Webホスティング、非同期タスク、Redis向けの次世代サーバーレスプラットフォームです。
多言語サポート
- Node.js、Python、Go、またはRustで開発できます。
無制限のプロジェクトを無料でデプロイ
- 使用量に応じてのみ料金が発生します - リクエストも料金もかかりません。
比類のない費用対効果
- アイドル料金なしの従量課金制。
- 例:25ドルで、平均応答時間60msで694万リクエストをサポートします。
合理化された開発者エクスペリエンス
- 簡単なセットアップのための直感的なUI。
- 完全に自動化されたCI/CDパイプラインとGitOps統合。
- 実用的な洞察のためのリアルタイムのメトリックとロギング。
容易なスケーラビリティと高性能
- 高い同時実行性を容易に処理するための自動スケーリング。
- 運用上のオーバーヘッドはゼロ - 構築に集中するだけです。
詳細については、ドキュメントをご覧ください!
Xでフォローしてください: @LeapcellHQ