Go言語のチャンネル(Channel)は、異なるgoroutine間でデータを安全にやり取りし、同期処理を実現するための強力な仕組みです。以下では、Goチャンネルに関する主要なポイントを紹介します。
基本的な概念
- 定義: `var dataCh chan string` は文字列型のチャンネルを定義します。
- 生成: `dataCh := make(chan string)` や `dataCh := make(chan string, 5)` でバッファ付きチャンネルを作成できます。
- 方向性: 単方向チャンネルとして `chan<- string` (送信専用)、`<-chan string` (受信専用)が利用可能です。
package main
import (
"fmt"
"time"
)
func sendData(out chan<- string) {
for i := 0; i < 4; i++ {
fmt.Printf("Sending: %d\n", i)
out <- fmt.Sprintf("Data-%d", i)
time.Sleep(1 * time.Second)
}
close(out)
}
func processData(in <-chan string) {
for v := range in {
fmt.Printf("Processing: %s\n", v)
time.Sleep(1 * time.Second)
}
}
func main() {
dataChan := make(chan string)
go sendData(dataChan)
go processData(dataChan)
time.Sleep(8 * time.Second)
fmt.Println("All tasks completed.")
}
操作方法
- 送信: `ch <- value` で値を送信します。
- 受信: `value := <- ch` で値を受け取ります。
- 複数受信: `value, ok := <- ch` でチャンネルが閉じられているか確認しながら受け取れます。
ブロッキングと非ブロッキング
無バッファチャンネルでは送信と受信が互いに待機状態になります。
package main
import (
"fmt"
)
func main() {
signalChan := make(chan int)
go func() {
for i := 0; i < 3; i++ {
fmt.Printf("Sender: Sending %d\n", i)
signalChan <- i
}
close(signalChan)
}()
for v := range signalChan {
fmt.Printf("Receiver: Received %d\n", v)
}
fmt.Println("All signals received.")
}
バッファ付きチャンネルでは、バッファがいっぱいになるまで送信がブロックされません。
package main
import (
"fmt"
)
func main() {
bufferedChan := make(chan int, 3)
go func() {
for i := 0; i < 6; i++ {
fmt.Printf("Sender: Sending %d\n", i)
bufferedChan <- i
}
close(bufferedChan)
}()
for v := range bufferedChan {
fmt.Printf("Receiver: Received %d\n", v)
}
fmt.Println("All items processed.")
}
チャンネルのクローズ
`close(ch)` を使用してチャンネルをクローズし、送信を禁止します。
Selectステートメント
複数の通信操作から選択を行う際に `select` ステートメントを利用します。
package main
import (
"fmt"
"time"
)
func main() {
resultChan := make(chan int)
go func() {
time.Sleep(2 * time.Second)
resultChan <- 88
}()
timeout := time.After(1 * time.Second)
for {
select {
case val := <-resultChan:
fmt.Println("Result:", val)
return
case <-timeout:
fmt.Println("Operation timed out")
return
default:
fmt.Println("Waiting...")
time.Sleep(300 * time.Millisecond)
}
}
}
範囲ループと注意点
チャンネルを `for range` で反復することで全てのデータを処理できます。また、チャンネルが不要になったら必ずクローズすることを心掛けましょう。
package main
import (
"fmt"
)
func main() {
taskChan := make(chan int, 4)
go func() {
for i := 0; i < 4; i++ {
fmt.Printf("Task A sending: %d\n", i)
taskChan <- i
}
}()
go func() {
for i := 10; i < 14; i++ {
fmt.Printf("Task B sending: %d\n", i)
taskChan <- i
}
}()
for j := 0; j < 8; j++ {
fmt.Printf("Received: %d\n", <-taskChan)
}
}