Go 言語は同時実行のために生まれました

Go 言語は同時実行のために生まれました

 
 
初期の CPU は、単一コアの形式で機械命令を順番に実行していました。 Go言語の祖先であるC言語は、この逐次型プログラミング言語の代表格です。逐次プログラミング言語のシーケンスは、すべての命令が逐次的に実行されると同時に、プログラムの命令を順番に実行する CPU が 1 つだけであることを意味します。

 

プロセッサ技術の発展に伴い、シングルコア時代では動作効率を向上させるためのプロセッサ周波数の向上がボトルネックとなりましたが、シングルコアCPU開発の停滞によりマルチコアCPU開発のチャンスが到来しました。これに対応して、プログラミング言語も徐々に並列化の方向に発展し始めています。

Javaの Netty など、一部のプログラミング言語フレームワークはマルチコア リソースの使用効率を常に向上させていますが、開発者はこれらのフレームワークを使いこなす前に、その動作原理を理解するために依然として多くの時間と労力を費やす必要があります。彼ら。

プログラマーにとって、ハードウェア リソースを最大限に活用できるアプリケーションを開発することは非常に困難です。現代のコンピューターには複数のコアがありますが、ほとんどのプログラミング言語には、プログラムがこれらのリソースを簡単に使用するための効果的なツールがありません。プログラミングするときは、複数のコアを利用するために大量のスレッド同期コードを記述する必要がありますが、これによりエラーが発生しやすくなります。

Go 言語は、マルチコアとネットワークのコンテキストで生まれた同時実行性をネイティブにサポートするプログラミング言語です。 Go 言語は、サードパーティのライブラリを必要とせずに、最下層からの同時実行をネイティブにサポートしており、開発者はプログラムを作成するときに CPU リソースの使用方法を簡単に決定できます。

Go 言語の同時実行性は goroutine に基づいており、Goroutine はスレッドに似ていますが、スレッドではありません。 goroutine は仮想スレッドとして理解できます。 Go 言語ランタイムはゴルーチンのスケジューリングに参加し、CPU パフォーマンスを最大化するために各 CPU にゴルーチンを合理的に割り当てます。

複数のゴルーチンの間で、Go 言語は通信にチャネルを使用します。チャネルは、ユーザーが異なるゴルーチン間で型指定されたメッセージを同期的に送信できるようにする組み込みのデータ構造です。これにより、プログラミング モデルは、複数のゴルーチンが同じデータへのアクセスを求めて競合するのではなく、ゴルーチン間でメッセージを送信することにさらに適応したものになります。

プログラムは、プロデューサー モードとコンシューマー モードとして同時実行性を必要とするリンクを設計し、データをチャネルに配置できます。次の図に示すように、チャネルのもう一方の端にあるコードはデータに対して同時計算を実行し、結果を返します。

 

ヒント: Go 言語は、チャネルを通じて複数の goroutine 間のメモリ共有を実現できます。

【例】プロデューサーは文字列を毎秒生成し、チャネルを通じてコン​​シューマに送信する プロデューサは2つのゴルーチンを使って同時実行し、コンシューマはmain()関数のゴルーチンで処理します。

 パッケージmain

インポート(
     "fmt"
     "math/rand"
     "time"
)

// データプロデューサ
func producer(header string, channel chan<- string) {
     // 無限ループで、データを停止することなく生成する
     for {
            // ランダムな数字と文字列を文字列にフォーマットしてチャンネルに送信する
            channel <- fmt.Sprintf("%s: %v", header, rand.Int31())
            // 1秒間待機する
            time.Sleep(time.Second)
        }
}

// データ消費者
func customer(channel <-chan string) {
     // データを停止することなく取得する
     for {
            // チャンネルからデータを取得します。
            // ここでは、データが返されるまでチャンネルでブロックされます。
            message := <-channel
            // データを出力する
            fmt.Println(message)
        }
}

func main() {
    // 文字列タイプのチャンネルを作成する
    channel := make(chan string)
    // producer()関数の並行ゴルーチンを作成する
    go producer("cat", channel)
    go producer("dog", channel)
    // データ消費関数
    customer(channel)
} 

操作結果:

dog: 2019727887
cat: 1298498081
dog: 939984059
cat: 1427131847
cat: 911902081
dog: 1474941318
dog: 140954425
cat: 336122540
cat: 208240456
dog: 646203300

コードの分析:

  • 行 03、インポート形式 (fmt)、乱数 (math/rand)、時間 (time) のパッケージがコンパイルに参加します。
  • 10 行目はデータを生成する関数で、タグ タイプの文字列と書き込みのみ可能なチャネルを渡します。
  • 13 行目で、for{} が無限ループを形成します。
  • 15 行目では、rand.Int31() を使用して乱数を生成し、fmt.Sprintf() 関数を使用してヘッダーと乱数を文字列にフォーマットします。
  • 18 行目で、time.Sleep() 関数を使用して 1 秒間一時停止してから、この関数を実行します。 goroutine 内で実行された場合、一時停止は他の goroutine の実行には影響しません。
  • 23 行目はデータを消費する関数で、書き込みのみ可能なチャネルを渡します。
  • 26 行目は、メッセージを継続的に消費するループを構築します。
  • 行 28 はチャネルからデータをフェッチします。
  • 行 31、取得したデータを出力します。
  • 35 行目はプログラムのエントリ関数であり、常にプログラムの先頭で実行されます。
  • 37 行目では、文字列型のチャネルをインスタンス化します。
  • 39 行目と 40 行目はプロデューサー関数を同時に実行し、この 2 行はそれぞれこの関数の異なるパラメーターを持つ 2 つのゴルーチンを作成します。
  • 行 42 は、チャネルを通じてデータを消費するためのコンシューマ関数を実行します。

コード全体では、スレッドの作成、スレッド プール、ロックはなく、キーワード go のみが goroutine の実装とチャネルとのデータ交換に使用されます。

 

「 Go 言語は同時実行のために生まれました」についてわかりやすく解説!絶対に観るべきベスト2動画

【たった1時間で学べる】Go言語のプログラミング初心者向けの超入門講座【文字書き起こし、ソースコードも完全無料!】
【初心者必見!】Go言語とは?できることや学ぶメリット・将来性について解説