0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Golang Channelのすべて:絶対に知っておくべきこと

Posted at

Group46.png

Leapcell:最適なGolangホスティング用サーバレスプラットフォーム

ChannelはGo言語のコア型です。コンカレントコアユニットがデータを送信または受信して通信を実現するためのパイプラインと見なすことができます。その演算子は矢印 <- です。

Channel操作の例

  • ch <- v:値 v をChannel ch に送信します。
  • v := <-ch:Channel ch からデータを受信し、そのデータを v に割り当てます。(矢印の向きはデータの流れ方向を示します。)

Channelの作成と使用

mapslice などのデータ型と同様に、Channelは使用前に作成する必要があります:

ch := make(chan int)

Channelの型

Channel型の定義形式は以下の通りです:

ChannelType = ( "chan" | "chan" "<-" | "<-" "chan" ) ElementType.

これには3種類の定義が含まれ、省略可能な <- はチャネルの方向を表します。方向が指定されていない場合、チャネルは双方向で、データの受信と送信の両方が可能です。

  • chan T:型 T のデータを受信および送信できます。
  • chan<- float64:型 float64 のデータを送信するためのみ使用できます。
  • <-chan int:型 int のデータを受信するためのみ使用できます。

<- は常に最も左の型と最初に結合します。例えば:

  • chan<- chan intchan<- (chan int) と同等です。
  • chan<- <-chan intchan<- (<-chan int) と同等です。
  • <-chan <-chan int<-chan (<-chan int) と同等です。
  • chan (<-chan int)

makeを使ったChannelの初期化と容量の設定

make(chan int, 100)

容量はChannelが保持できる最大要素数、つまりChannelのバッファサイズを表します。容量が設定されていない、または0に設定されている場合、これはChannelにバッファがないことを意味し、送信者と受信者の両方が準備できたときにのみ通信が行われます(ブロッキング)。バッファを設定した後は、ブロッキングが発生しない場合があります。バッファがいっぱいになったときにのみ send 操作がブロックされ、バッファが空のときには receive 操作がブロックされます。nil チャネルは通信しません。

Channelのクローズ

Channelは組み込みの close メソッドを通じてクローズできます。複数のgoroutineは追加の同期対策を考えることなく、チャネルからデータを receive したり、チャネルにデータを send したりすることができます。Channelは先入先出(FIFO)キューとして機能し、データの受信と送信の順序は一貫しています。

Channelの受信は複数値の代入をサポート

v, ok := <-ch

この方法を使用して、Channelがクローズされたかどうかをチェックできます。

send文

send文はChannelにデータを送信するために使用され、例えば ch <- 3 のようになります。その定義は以下の通りです:

SendStmt = Channel "<-" Expression.
Channel  = Expression.

通信が開始される前に、チャネルと式の両方が評価される必要があります。例えば:

c := make(chan int)
defer close(c)
go func() { c <- 3 + 4 }()
i := <-c
fmt.Println(i)

上記のコードでは、まず (3 + 4) が7に計算され、その後チャネルに送信されます。送信が実行されるまで通信はブロックされます。前述のように、バッファのないチャネルの場合、送信操作は受信者が準備できたときにのみ実行されます。バッファがあり、バッファがいっぱいでない場合、送信操作は実行されます。既にクローズされたチャネルにデータを送信し続けると、実行時パニックが発生します。nil チャネルにデータを送信すると、無期限にブロックされます。

receive演算子

<-ch はチャネル ch からデータを受信するために使用されます。この式はデータを受信できるまでブロックされます。nil チャネルからデータを受信すると、無期限にブロックされます。既にクローズされたチャネルからデータを受信すると、ブロックされずに直ちに返ります。送信されたデータを受信した後、要素型のゼロ値を返します。前述のように、追加の戻り値を使用して、チャネルがクローズされたかどうかをチェックできます:

x, ok := <-ch
x, ok = <-ch
var x, ok = <-ch

OKfalse の場合、受信した x は生成されたゼロ値であり、チャネルがクローズされているか空であることを示します。

ブロッキング

デフォルトでは、送信と受信は相手が準備できるまでブロックされます。この方法を使用すると、明示的なロックや条件変数を使用せずに、goroutine内で同期を行うことができます。例えば、公式の例:

import "fmt"
func sum(s []int, c chan int) {
    sum := 0
    for _, v := range s {
        sum += v
    }
    c <- sum // 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)
}

上記のコードでは、x, y := <-c, <-c という文は計算結果がチャネルに送信されるのを待ち続けます。

バッファ付きチャネル

make の2番目のパラメータはバッファのサイズを指定します:

ch := make(chan int, 100)

バッファを使用することで、できるだけブロッキングを回避し、アプリケーションのパフォーマンスを向上させることができます。

Range

for …… range 文はチャネルを処理できます:

func main() {
    go func() {
        time.Sleep(1 * time.Hour)
    }()
    c := make(chan int)
    go func() {
        for i := 0; i < 10; i = i + 1 {
            c <- i
        }
        close(c)
    }()
    for i := range c {
        fmt.Println(i)
    }
    fmt.Println("Finished")
}

range c で生成される反復値は、Channelに送信された値です。チャネルがクローズされるまで繰り返します。上記の例で、close(c) をコメントアウトすると、プログラムは for …… range の行でブロックされます。

select

select 文は、一連の可能な送信と受信操作を選択して処理するために使用されます。switch に似ていますが、通信操作の処理にのみ使用されます。その case は送信文、受信文、または default のいずれかです。受信文は1つまたは2つの変数に値を割り当てることができ、必ず受信操作でなければなりません。最大で1つの default ケースが許され、通常はケースリストの最後に配置されます。例えば:

import "fmt"
func fibonacci(c, quit chan int) {
    x, y := 0, 1
    for {
        select {
        case c <- x:
            x, y = y, x+y
        case <-quit:
            fmt.Println("quit")
            return
        }
    }
}
func main() {
    c := make(chan int)
    quit := make(chan int)
    go func() {
        for i := 0; i < 10; i++ {
            fmt.Println(<-c)
        }
        quit <- 0
    }()
    fibonacci(c, quit)
}

複数の case が同時に処理可能な場合、例えば複数のチャネルが同時にデータを受信できる場合、Goは疑似的にランダムに1つの case を選択して処理します(疑似的にランダム)。処理する case がない場合、default が選択されて処理されます(前提は default case が存在すること)。default case が存在しない場合、select 文は処理する case があるまでブロックされます。nil チャネルに対する操作は無期限にブロックされることに注意してください。default case がない場合、nil チャネルのみを含む select は無期限にブロックされます。select 文は switch 文と同様に、ループではなく、1つの case のみを選択して処理します。チャネルを継続的に処理するには、外部に無限ループ for を追加できます:

for {
    select {
    case c <- x:
        x, y = y, x+y
    case <-quit:
        fmt.Println("quit")
        return
    }
}

タイムアウト

select の1つの重要な応用はタイムアウト処理です。処理する case がない場合、select 文はブロックされるため、このときタイムアウト操作が必要な場合があります。例えば:

import "time"
import "fmt"
func main() {
    c1 := make(chan string, 1)
    go func() {
        time.Sleep(time.Second * 2)
        c1 <- "result 1"
    }()
    select {
    case res := <-c1:
        fmt.Println(res)
    case <-time.After(time.Second * 1):
        fmt.Println("timeout 1")
    }
}

上記の例では、2秒後にチャネル c1 にデータが送信されますが、select は1秒後にタイムアウトするように設定されています。したがって、result 1 ではなく timeout 1 が出力されます。ここでは time.After メソッドを使用しています。このメソッドは型 <-chan Time の片方向チャネルを返します。指定された時間に、現在の時刻が返されるチャネルに送信されます。

TimerとTicker

  1. Timer:これは1つの将来のイベントを表すタイマーです。待機時間を指定でき、チャネルを提供します。指定された時間に、チャネルに時刻値が提供されます。例えば:
timer1 := time.NewTimer(time.Second * 2)
<-timer1.C
fmt.Println("Timer 1 expired")

上記の2行目は約2秒間ブロックされ、時間が到来した後に続けて実行されます。もちろん、単に待機するだけなら、time.Sleep を使用して達成できます。また、timer.Stop を使用してタイマーを停止することもできます:

timer2 := time.NewTimer(time.Second)
go func() {
    <-timer2.C
    fmt.Println("Timer 2 expired")
}()
stop2 := timer2.Stop()
if stop2 {
    fmt.Println("Timer 2 stopped")
}
  1. Ticker:これは定期的にトリガーされるタイマーです。一定期間(間隔)でイベント(現在の時刻)をチャネルに送信します。チャネルの受信者は、一定の時間間隔でチャネルからイベントを読み取ることができます。例えば:
ticker := time.NewTicker(time.Millisecond * 500)
go func() {
    for t := range ticker.C {
        fmt.Println("Tick at", t)
    }
}()

timer と同様に、tickerStop メソッドを通じて停止できます。停止すると、受信者はもはやチャネルからデータを受信できなくなります。

close

組み込みの close メソッドを使用してチャネルをクローズできます。チャネルがクローズされた後の送信者と受信者の操作をまとめると以下の通りです:

  • チャネル c がクローズされている場合、そこにデータを送信し続けるとパニックが発生します:send on closed channel。例えば:
import "time"
func main() {
    go func() {
        time.Sleep(time.Hour)
    }()
    c := make(chan int, 10)
    c <- 1
    c <- 2
    close(c)
    c <- 3
}
  • クローズされたチャネルからは送信されたデータを読み取ることができるだけでなく、ゼロ値を読み続けることもできます:
c := make(chan int, 10)
c <- 1
c <- 2
close(c)
fmt.Println(<-c) //1
fmt.Println(<-c) //2
fmt.Println(<-c) //0
fmt.Println(<-c) //0
  • range を使って読み取る場合、チャネルがクローズされた後、for ループは抜け出します:
c := make(chan int, 10)
c <- 1
c <- 2
close(c)
for i := range c {
    fmt.Println(i)
}
  • i, ok := <-c を使うと、チャネルの状態を確認し、値がゼロ値なのか通常に読み取った値なのかを判断できます:
c := make(chan int, 10)
close(c)
i, ok := <-c
fmt.Printf("%d, %t", i, ok) //0, false

同期

チャネルはゴルーチン間の同期に使用できます。例えば、次の例ではメインゴルーチンが done チャネルを通じてワーカーがタスクを完了するのを待ちます。ワーカーはタスクを完了した後、チャネルにデータを送信することでメインゴルーチンにタスクの完了を通知することができます:

import (
    "fmt"
    "time"
)
func worker(done chan bool) {
    time.Sleep(time.Second)
    // タスクが完了したことを通知
    done <- true
}
func main() {
    done := make(chan bool, 1)
    go worker(done)
    // タスクが完了するのを待つ
    <-done
}

Leapcell:最適なGolangホスティング用サーバレスプラットフォーム

最後に、Golangのデプロイに最適なプラットフォームをおすすめします:Leapcell

barndpic.png

1. 多言語対応

  • JavaScript、Python、Go、またはRustで開発できます。

2. 無制限のプロジェクトを無料でデプロイ

  • 使用量に応じてのみ課金 — リクエストがなければ料金はかかりません。

3. 圧倒的なコスト効率

  • 使い放題でアイドル料金はかかりません。
  • 例:25ドルで平均応答時間60msで694万回のリクエストをサポートできます。

4. 合理化された開発者体験

  • 直感的なUIによる簡単なセットアップ。
  • 完全自動化されたCI/CDパイプラインとGitOps統合。
  • アクション可能なインサイトを得るためのリアルタイムメトリックとロギング。

5. 簡単なスケーラビリティと高パフォーマンス

  • 高い並行性を簡単に処理するための自動スケーリング。
  • オペレーションオーバーヘッドゼロ — 構築に集中できます。

Frame3-withpadding2x.png

ドキュメントで詳細を探索!

LeapcellのTwitter:https://x.com/LeapcellHQ

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?