プログラミング言語 golang go concurrent Go言語のマルチコア並列化

Go言語のマルチコア並列化

 
 
Go言語は高い同時実行性をサポートする特徴があり、マルチスレッド処理を容易に実装でき、マルチコアCPUの性能を最大限に活用できます。

 

ご存知のとおり、ほとんどのサーバー プロセッサはシングルコアの周波数が低く、コアの数が多いため、高い同時実行性をサポートするプログラミング言語では、サーバーのマルチコアの利点を最大限に活用することができ、その結果、シングルコアの負荷を軽減し、パフォーマンスの無駄を削減します。

Go 言語ではマルチコアとマルチスレッドの同時処理を実装すると非常に便利です。

package main

import(
    "fmt"
)

func main() {
    for i := 0; i < 5; i++ {
        go AsyncFunc(i)
    }
}

func AsyncFunc(index int) {
    sum := 0
    for i := 0; i < 10000; i++ {
        sum += 1
    }
    fmt.Printf("スレッド%d, sum:%d\n", index, sum)
} 

操作の結果は次のようになります。

スレッド0, sum:10000
スレッド2, sum:10000
スレッド3, sum:10000
スレッド1, sum:10000
スレッド4, sum:10000

高価なコンピューティング タスクを実行する場合、最新のサーバーに共通のマルチコア機能を利用してタスクを可能な限り並列化し、合計コンピューティング時間を短縮したいと考えています。この時点で、CPU コアの数を理解し、コンピューティング タスクを複数のゴルーチンに分解して並列実行する必要があります。

N 個の整数の合計を計算する、完全な並列計算タスクをシミュレートしてみましょう。すべての整数は M 個の部分に分割できます。M は CPU の数です。各 CPU に割り当てられた計算タスクの計算を開始させ、最後に各 CPU の計算結果を再度累積して、N 個の整数すべての合計を取得します。

 typeベクタ[]float64
//各CPUの割り当てられた計算タスク
func (v Vector) DoSome(i、n int、u Vector、c chan int) {
    for; i < n; i ++ {
        v [i] + = u.Op(v [i])
    }
    
    c <- 1 //タスクマネージャーに計算が完了したことを通知するシグナルを送信します。
}
const NCPU = 16 // 仮定されるコア数は16
func (v Vector) DoAll(u Vector) {
    c := make(chan int、NCPU) //各CPUのタスク完了シグナルを受信するためのもの
    
    for i := 0; i < NCPU; i ++ {
        go v.DoSome(i*len(v)/NCPU、(i+1)*len(v)/NCPU、u、c)
    }
    
    //すべてのCPUのタスクが完了するのを待ちます。
    for i := 0; i < NCPU; i ++ {
        <-c //データを受信すると、1つのCPUが計算を完了したことを意味します。
    }
    
    //ここで、すべての計算が終了しました。 

これら 2 つの関数はうまく設計されているようで、そのうちの DoAll() は CPU コアの数に応じてタスクを分割し、複数のゴルーチンを開いてこれらの計算タスクを並列実行します。

総計算時間を元のほぼ 1/N に短縮することは可能でしょうか?答えは必ずしもそうではありません。ストップウォッチをつまんでみると、合計実行時間が大幅に短縮されていないことがわかります。 CPUの動作状況を観察してみると、16個のCPUコアがあるにもかかわらず、実際に計算処理中にビジー状態にあるのは1つのCPUコアだけであり、多くのGo言語初心者を混乱させる問題です。

公式の答えは、Go コンパイラの現在のバージョンでは、マルチコアの利点をインテリジェントに発見して活用することができない、というものです。複数のゴルーチンを作成し、実行状態からはそれらのゴルーチンも並行して実行されていますが、実際にはこれらのゴルーチンはすべて同じCPUコア上で実行されており、あるゴルーチンがタイムスライス実行を受けると、他のゴルーチンが待機することになります。この点から、ゴルーチンは並列コードを記述するプロセスを簡素化しますが、実際には全体的な動作効率はシングルスレッド プログラムよりもそれほど高くないことがわかります。

Go 言語ではマルチコアの利点を十分に活用できませんが、環境変数 GOMAXPROCS の値を設定することで、まず使用する CPU コアの数を制御できます。具体的な操作方法は、環境変数 GOMAXPROCS の値を直接設定するか、コード内で goroutine を開始する前に次のステートメントを呼び出して 16 個の CPU コアの使用を設定します。

runtime.GOMAXPROCS(16)

CPU コアの数を設定する必要がありますか? 実際、ランタイム パッケージには、コアの数を取得するための別の NumCPU() 関数も用意されています。サンプル コードは次のとおりです。

package main
 
import (
"fmt"
"runtime"
)
 
func main() {
  cpuNum := runtime.NumCPU() //現在のデバイスのCPUコア数を取得する
  fmt.Println("CPUコア数:", cpuNum)
 
  runtime.GOMAXPROCS(cpuNum) //使用する必要のあるCPU数を設定する
} 

操作の結果は次のようになります。

cpuコア数: 4

 

「 Go言語のマルチコア並列化」についてわかりやすく解説!絶対に観るべきベスト2動画

Go言語とは?|プログラミング言語のGo言語について3分でわかりやすく解説します【プログラミング初心者向け】
GO言語でAPI開発「 gRPC 」入門