ホームページ  >  記事  >  バックエンド開発  >  Go の同時実行性: 基本から高度な概念まで

Go の同時実行性: 基本から高度な概念まで

Linda Hamilton
Linda Hamiltonオリジナル
2024-10-03 06:11:30426ブラウズ

Concurrency in Go: From Basics to Advanced Concepts

目次

  1. 同時実行の概要
  2. 同時実行性と並列性
  3. ゴールーチン: 同時実行の構成要素
  4. チャネル: Go ルーチン間の通信
  5. Select ステートメント: 複数のチャネルの管理
  6. 同期プリミティブ
  7. 同時実行パターン
  8. コンテキストパッケージ: キャンセルの管理と タイムアウト。
  9. ベスト プラクティスと一般的な落とし穴**

1.並行性の概要

同時実行性とは、複数のタスクを同時に処理できる機能です。 Go では、同時実行性は第一級の要素であり、言語のコア設計に組み込まれています。 Go の同時実行性へのアプローチは、共有メモリではなくプロセス間の通信を重視するモデルである Communicating Sequential Processes (CSP) に基づいています。

2.同時実行性と並列性:

Go ルーチンにより、独立して実行されるプロセスの構成である並行性が可能になります。
システムに複数の CPU コアがあり、Go ランタイムが go ルーチンを並列実行するようにスケジュールしている場合、並列処理 (同時実行) が発生する可能性があります。

3.ゴールーチン:
同時実行の構成要素は Go ルーチンであり、Go ランタイムによって管理される軽量のスレッドです。これは、他の関数またはメソッドと同時に実行される関数またはメソッドです。 Go ルーチンは Go の同時実行モデルの基礎です。

主な特徴:

  • 軽量: Go ルーチンは OS スレッドよりもはるかに軽量です。パフォーマンスに大きな影響を与えることなく、何千もの go-routine を簡単に作成できます。
  • Go ランタイムによって管理: Go スケジューラは、利用可能な OS スレッド全体での go ルーチンの配布を処理します。
  • 安価な作成: go ルーチンの開始は、関数呼び出しの前に go キーワードを使用するだけで簡単です。
  • スタック サイズ: Go ルーチンは、必要に応じて拡大および縮小できる小さなスタック (約 2KB) から始まります。

ゴールーチンの作成:
go ルーチンを開始するには、go キーワードに続いて関数呼び出しを使用するだけです:

go functionName()

または匿名関数を使用します:

go func() {
    // function body
}()

通常のスケジュール:

  • Go ランタイムは M:N スケジューラーを使用し、M 個の go ルーチンが N OS スレッドにスケジュールされます。
  • このスケジューラは非プリエンプティブです。つまり、ゴルーチンがアイドル状態または論理的にブロックされている場合に制御を譲ります。

通信と同期:

  • Goroutine は通常、「メモリの共有によって通信するのではなく、通信によってメモリを共有する」という原則に従って、チャネルを使用して通信します。
  • 単純な同期の場合、sync.WaitGroup や sync.Mutex などのプリミティブを使用できます。

説明付きの例:

package main

import (
    "fmt"
    "time"
)

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

func printLetters() {
    for i := 'a'; i <= 'e'; i++ {
        time.Sleep(150 * time.Millisecond)
        fmt.Printf("%c ", i)
    }
}

func main() {
    go printNumbers()
    go printLetters()
    time.Sleep(2 * time.Second)
    fmt.Println("\nMain function finished")
}

説明:

  • printNumbers と printLetters という 2 つの関数を定義します。
  • main では、go キーワードを使用してこれらの関数をゴルーチンとして開始します。
  • その後、main 関数は 2 秒間スリープして、ゴルーチンが完了できるようにします。
  • ゴルーチンがなければ、これらの関数は順番に実行されます。 goroutine を使用すると、それらは同時に実行されます。
  • 出力には数字と文字が交互に表示され、同時実行を示します。

ゴルーチンのライフサイクル:

  • ゴルーチンは go キーワードを使用して作成されると開始されます。
  • 関数が完了するかプログラムが終了すると終了します。
  • Goroutine は適切に管理しないと漏洩する可能性があるため、Goroutine が終了できることを確認することが重要です。

ベストプラクティス:

  • ライブラリ内にゴルーチンを作成しないでください。呼び出し元に同時実行性を制御させます。
  • 無制限の数のゴルーチンを作成する場合は注意してください。
  • チャネルまたは同期プリミティブを使用してゴルーチン間を調整します。
  • 複数の goroutine を効率的に管理するには、ワーカー プールの使用を検討してください。

ゴールーチンの説明を含む簡単な例

package main

import (
    "fmt"
    "time"
)

// printNumbers is a function that prints numbers from 1 to 5
// It will be run as a goroutine
func printNumbers() {
    for i := 1; i <= 5; i++ {
        time.Sleep(500 * time.Millisecond) // Sleep for 500ms to simulate work
        fmt.Printf("%d ", i)
    }
}

// printLetters is a function that prints letters from 'a' to 'e'
// It will also be run as a goroutine
func printLetters() {
    for i := 'a'; i <= 'e'; i++ {
        time.Sleep(300 * time.Millisecond) // Sleep for 300ms to simulate work
        fmt.Printf("%c ", i)
    }
}

func main() {
    // Start printNumbers as a goroutine
    // The 'go' keyword before the function call creates a new goroutine
    go printNumbers()

    // Start printLetters as another goroutine
    go printLetters()

    // Sleep for 3 seconds to allow goroutines to finish
    // This is a simple way to wait, but not ideal for production code
    time.Sleep(3 * time.Second)

    // Print a newline for better formatting
    fmt.Println("\nMain function finished")
}

4.チャンネル:

チャネルは、Go ルーチンが相互に通信し、実行を同期できるようにする Go の中核機能です。これらは、ある go-routine が別の go-routine にデータを送信する方法を提供します。

チャンネルの目的

Go のチャネルは 2 つの主な目的を果たします:
a) 通信: ゴルーチンが相互に値を送受信できるようにします。
b) 同期: ゴルーチン間で実行を同期するために使用できます。

作成: チャンネルは make 関数を使用して作成されます:

ch := make(chan int)  // Unbuffered channel of integers

送信: <- 演算子を使用して値がチャネルに送信されます:

ch <- 42  // Send the value 42 to the channel

Receiving: Values are received from a channel using the <- operator:

value := <-ch  // Receive a value from the channel

Types of Channels

a) Unbuffered Channels:

  • Created without a capacity: ch := make(chan int)
  • Sending blocks until another goroutine receives.
  • Receiving blocks until another goroutine sends.
ch := make(chan int)
go func() {
    ch <- 42  // This will block until the value is received
}()
value := <-ch  // This will receive the value

b) Buffered Channels:

  • Created with a capacity: ch := make(chan int, 3)
  • Sending only blocks when the buffer is full.
  • Receiving only blocks when the buffer is empty.
ch := make(chan int, 2)
ch <- 1  // Doesn't block
ch <- 2  // Doesn't block
ch <- 3  // This will block until a value is received

Channel Directions

Channels can be directional or bidirectional:

  • Bidirectional: chan T
  • Send-only: chan<- T
  • Receive-only: <-chan T

Example :

func send(ch chan<- int) {
    ch <- 42
}

func receive(ch <-chan int) {
    value := <-ch
    fmt.Println(value)
}

Closing Channels

Channels can be closed to signal that no more values will be sent:

close(ch)

Receiving from a closed channel:

If the channel is empty, it returns the zero value of the channel's type.
You can check if a channel is closed using a two-value receive:

value, ok := <-ch
if !ok {
    fmt.Println("Channel is closed")
}

Ranging over Channels

You can use a for range loop to receive values from a channel until it's closed:

for value := range ch {
    fmt.Println(value)
}

Hey, Thank you for staying until the end! I appreciate you being valuable reader and learner. Please follow me here and also on my Linkedin and GitHub .

以上がGo の同時実行性: 基本から高度な概念までの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。