2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

Goは並行処理を簡単に、そして効率的に実装できるプログラミング言語として知られています。その中心的な概念が「ゴルーチン」と「チャネル」です。本記事は、「ゴルーチン」と「チャネル」について学んだことのアウトプットです。

ゴルーチン

概念

ゴルーチンは、Goプログラムで並行処理を実現するための基本的なスレッドです。従来のOSスレッドと比較して、非常に軽量で、少ないリソースで並行処理を実行することができます。

Goでは、ゴルーチンを使って並行処理を行う際に、通常、関数の呼び出し時にgoを付けてその関数を別スレッド(ゴルーチン)として実行させます。これにより、プログラム内で複数のゴルーチンが並行して実行されるようになります。

ゴルーチンとOSスレッドの違い

 OSスレッドの場合、OSがスレッドのスケジューリングを管理します。これにはコンテキストスイッチング(スレッドの切り替え)が含まれますが、このプロセスは時間がかかります。
 一方、ゴルーチンはGoランタイムによって管理されており、コンテキストスイッチが高速で、ゴルーチン間の切り替えが容易であるため、並行処理を効率的に行うことができます。

主な特徴

  • 非常に軽量(数キロバイトのメモリしか消費しない)
  • 簡単に生成できる
  • 多数のゴルーチンを同時に実行可能
  • OSのスレッドとは異なり、Goランタイムによって管理される

実装して理解する

package main

import (
    "fmt"
    "time"
)

func printNumbers() {
    for i := 1; i <= 5; i++ {
        time.Sleep(100 * time.Millisecond)
        fmt.Printf("Number: %d\n", i)
    }
}

func printLetters() {
    for letter := 'A'; letter <= 'E'; letter++ {
        time.Sleep(150 * time.Millisecond)
        fmt.Printf("Letter: %c\n", letter)
    }
}

func main() {
    go printNumbers()
    go printLetters()

    // メインゴルーチンが終了しないよう、待ち時間を入れる
    time.Sleep(1 * time.Second)
    fmt.Println("Main goroutine finished")
}

このコードでは、printNumbers()printLetters() の2つの関数を別々のゴルーチンで実行しています。go キーワードを使うことで、関数が並行して実行されます。

動作確認

go run main.go

以下のようにNumberとLetterの動作が同時に動いていることが見て取れます。

Number: 1
Letter: A
Number: 2
Letter: B
Number: 3
Number: 4
Letter: C
Number: 5
Letter: D
Letter: E
Main goroutine finished

ゴルーチンを使わなかった時の出力

printNumbers() の処理が終わってから printLetters() を呼び出していることがわかりますね。

Number: 1
Number: 2
Number: 3
Number: 4
Number: 5
Letter: A
Letter: B
Letter: C
Letter: D
Letter: E
Main goroutine finished

チャネル

概念

チャネルは、ゴルーチン間で安全にデータをやり取りするための仕組みです。データを送信したり、受信したりするために使用され、ゴルーチン間の通信を可能にします。

主な特徴

  • 型付きのパイプのような通信機構
  • 送信と受信をブロックする
  • デッドロックを防ぐための組み込みのメカニズム
  • バッファありとバッファなしの2種類が存在

実装して理解する

package main

import (
    "fmt"
    "time"
)

func worker(id int, jobs <-chan int, results chan<- int) {
    for job := range jobs {
        fmt.Printf("Worker %d processing job %d\n", id, job)
        time.Sleep(time.Second)
        results <- job * 2
    }
}

func main() {
    jobs := make(chan int, 10)
    results := make(chan int, 10)

    // 3つのワーカーゴルーチンを起動
    for w := 1; w <= 3; w++ {
        go worker(w, jobs, results)
    }

    // ジョブを送信
    for j := 1; j <= 5; j++ {
        jobs <- j
    }
    close(jobs)

    // 結果を収集
    for a := 1; a <= 5; a++ {
        fmt.Println(<-results)
    }
}

コード説明

3つのワーカーゴルーチンが並行に動作

for w := 1; w <= 3; w++ {
    go worker(w, jobs, results)
}

rangeを使用してジョブチャネルからデータが渡された時のみ動作する

func worker(id int, jobs <-chan int, results chan<- int) {
    for job := range jobs {
        fmt.Printf("Worker %d processing job %d\n", id, job)
        time.Sleep(time.Second)
        results <- job * 2
    }
}

ジョブは利用可能なワーカーに自動的に分配

// ジョブを送信
for j := 1; j <= 5; j++ {
    jobs <- j
}

結果は別のチャネルで収集

results <- job * 2

補足

  • 送信側がデータをチャネルに送るとき、受信側が受け取るまで処理はブロック(停止)されます。
  • 受信側も送信側からデータが来るまでブロックされます。
  • close(chan)を使ってチャネルを明示的に閉じることができます。

参考

2
1
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
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?