
こんにちは。よっしーです(^^)
本日は、Go言語のよくある質問 について解説しています。
背景
Go言語を学んでいると「なんでこんな仕様になっているんだろう?」「他の言語と違うのはなぜ?」といった疑問が湧いてきませんか。Go言語の公式サイトにあるFAQページには、そんな疑問に対する開発チームからの丁寧な回答がたくさん載っているんです。ただ、英語で書かれているため読むのに少しハードルがあるのも事実で、今回はこのFAQを日本語に翻訳して、Go言語への理解を深めていけたらと思い、これを読んだ時の内容を備忘として残しました。
Concurrency
CPU数をどのように制御できますか?
実行中のgoroutineが同時に使用できるCPU数は、GOMAXPROCS
シェル環境変数によって制御され、そのデフォルト値は利用可能なCPUコア数です。並列実行の可能性を持つプログラムは、したがってマルチCPUマシンでデフォルトでそれを達成するべきです。使用する並列CPU数を変更するには、環境変数を設定するか、runtimeパッケージの同様の名前の関数を使用して、異なる数のスレッドを利用するようにランタイムサポートを設定します。これを1に設定すると、真の並列性の可能性を排除し、独立したgoroutineが順番に実行されることを強制します。
ランタイムは複数の未処理I/Oリクエストにサービスを提供するために、GOMAXPROCS
の値よりも多くのスレッドを割り当てることができます。GOMAXPROCS
は実際に一度に実行できるgoroutineの数のみに影響します。任意により多くのgoroutineがシステムコールでブロックされる可能性があります。
Goのgoroutineスケジューラは、goroutineとスレッドのバランスを取ることに優れており、同じスレッド上の他のものが飢餓状態にならないようにgoroutineの実行を先取りすることさえできます。しかし、それは完璧ではありません。パフォーマンスの問題が見られる場合、アプリケーションごとにGOMAXPROCS
を設定することが役立つかもしれません。
解説
この節では、Go言語におけるCPU使用数の制御方法について説明されています。GOMAXPROCS
の理解と適切な設定は、並列プログラムのパフォーマンス最適化において重要です。
GOMAXPROCS の基本概念
現在の設定確認と変更
func demonstrateGOMAXPROCS() {
fmt.Println("GOMAXPROCS の基本操作:")
// 現在の設定を確認
currentProcs := runtime.GOMAXPROCS(0) // 0を渡すと現在値を返す
numCPU := runtime.NumCPU()
fmt.Printf("利用可能CPU数: %d\n", numCPU)
fmt.Printf("現在のGOMAXPROCS: %d\n", currentProcs)
// 環境変数からの確認
if envProcs := os.Getenv("GOMAXPROCS"); envProcs != "" {
fmt.Printf("環境変数GOMAXPROCS: %s\n", envProcs)
} else {
fmt.Printf("環境変数GOMAXPROCS: 未設定(デフォルト値使用)\n")
}
// プログラム実行時の変更
fmt.Println("\nGOMAXPROCSの動的変更:")
// 1に設定(並列性を無効化)
oldProcs := runtime.GOMAXPROCS(1)
fmt.Printf("GOMAXPROCS を %d から 1 に変更\n", oldProcs)
// 並列性のテスト
testParallelism("GOMAXPROCS=1")
// CPU数に設定
runtime.GOMAXPROCS(numCPU)
fmt.Printf("GOMAXPROCS を %d に変更\n", numCPU)
testParallelism(fmt.Sprintf("GOMAXPROCS=%d", numCPU))
// 元の値に戻す
runtime.GOMAXPROCS(oldProcs)
fmt.Printf("GOMAXPROCS を元の値 %d に戻す\n", oldProcs)
}
func testParallelism(label string) {
fmt.Printf("\n%s での並列性テスト:\n", label)
var wg sync.WaitGroup
numGoroutines := 4
start := time.Now()
for i := 0; i < numGoroutines; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
// CPU集約的な作業
sum := 0
for j := 0; j < 50000000; j++ {
sum += j
}
duration := time.Since(start)
fmt.Printf(" Goroutine %d 完了: %v (sum=%d)\n", id, duration, sum%1000)
}(i)
}
wg.Wait()
totalTime := time.Since(start)
fmt.Printf(" 総実行時間: %v\n", totalTime)
}
環境変数による制御
異なる設定での実行例
# デフォルト(CPU数と同じ)
go run main.go
# 単一CPU使用
GOMAXPROCS=1 go run main.go
# 特定の数のCPU使用
GOMAXPROCS=2 go run main.go
# CPU数より多く設定(通常は効果なし)
GOMAXPROCS=16 go run main.go
func demonstrateEnvironmentControl() {
fmt.Println("環境変数による制御の例:")
// 現在の設定情報を表示
showCurrentSettings := func() {
fmt.Printf(" GOMAXPROCS: %d\n", runtime.GOMAXPROCS(0))
fmt.Printf(" NumCPU: %d\n", runtime.NumCPU())
fmt.Printf(" NumGoroutine: %d\n", runtime.NumGoroutine())
}
fmt.Println("\n初期設定:")
showCurrentSettings()
// シミュレーション:異なる GOMAXPROCS 値での動作
settings := []int{1, 2, 4, runtime.NumCPU()}
for _, procs := range settings {
if procs > runtime.NumCPU() {
continue
}
fmt.Printf("\n--- GOMAXPROCS=%d でのテスト ---\n", procs)
oldProcs := runtime.GOMAXPROCS(procs)
// 並列処理のベンチマーク
result := benchmarkCPUWork(procs)
fmt.Printf("実行時間: %v\n", result)
showCurrentSettings()
runtime.GOMAXPROCS(oldProcs) // 元に戻す
}
}
func benchmarkCPUWork(expectedParallelism int) time.Duration {
start := time.Now()
var wg sync.WaitGroup
// CPU集約的なタスクを実行
numTasks := expectedParallelism * 2 // 並列度の2倍のタスク
for i := 0; i < numTasks; i++ {
wg.Add(1)
go func(taskID int) {
defer wg.Done()
// 計算集約的な処理
result := 0
for j := 0; j < 10000000; j++ {
result += j * j % 1000
}
// 結果を使用(最適化による削除を防ぐ)
_ = result
}(i)
}
wg.Wait()
return time.Since(start)
}
I/O バウンド vs CPU バウンドの影響
GOMAXPROCS の異なる影響
func demonstrateIOvsCPUBound() {
fmt.Println("I/OバウンドとCPUバウンドでのGOMAXPROCSの影響:")
// CPU バウンドなタスク
cpuBoundTask := func(numGoroutines int) time.Duration {
start := time.Now()
var wg sync.WaitGroup
for i := 0; i < numGoroutines; i++ {
wg.Add(1)
go func() {
defer wg.Done()
// CPU集約的な処理
sum := 0.0
for j := 0; j < 1000000; j++ {
sum += math.Sin(float64(j)) * math.Cos(float64(j))
}
_ = sum
}()
}
wg.Wait()
return time.Since(start)
}
// I/O バウンドなタスク
ioBoundTask := func(numGoroutines int) time.Duration {
start := time.Now()
var wg sync.WaitGroup
for i := 0; i < numGoroutines; i++ {
wg.Add(1)
go func() {
defer wg.Done()
// I/O集約的な処理(スリープでシミュレート)
time.Sleep(10 * time.Millisecond)
}()
}
wg.Wait()
return time.Since(start)
}
numCPU := runtime.NumCPU()
// 異なる GOMAXPROCS 設定でテスト
for _, procs := range []int{1, numCPU/2, numCPU, numCPU * 2} {
if procs <= 0 {
continue
}
fmt.Printf("\n--- GOMAXPROCS=%d ---\n", procs)
oldProcs := runtime.GOMAXPROCS(procs)
// CPUバウンドタスクのテスト
cpuTime := cpuBoundTask(numCPU)
fmt.Printf("CPUバウンド (%d goroutines): %v\n", numCPU, cpuTime)
// I/Oバウンドタスクのテスト
ioTime := ioBoundTask(100) // より多くのgoroutines
fmt.Printf("I/Oバウンド (100 goroutines): %v\n", ioTime)
runtime.GOMAXPROCS(oldProcs)
}
fmt.Println("\n観察ポイント:")
fmt.Println("• CPUバウンド: GOMAXPROCS=CPU数で最適")
fmt.Println("• I/Oバウンド: GOMAXPROCSの影響は限定的")
fmt.Println("• I/O待機中は他のgoroutineが実行される")
}
実際のスレッド数とGOMAXPROCSの関係
スレッド使用量の監視
func demonstrateThreadUsage() {
fmt.Println("実際のスレッド使用量の監視:")
// スレッド数を監視する関数
monitorThreads := func(label string, duration time.Duration) {
fmt.Printf("\n--- %s ---\n", label)
ticker := time.NewTicker(100 * time.Millisecond)
defer ticker.Stop()
done := time.After(duration)
for {
select {
case <-ticker.C:
var m runtime.MemStats
runtime.ReadMemStats(&m)
fmt.Printf("Goroutines: %d, Threads: %d, GOMAXPROCS: %d\n",
runtime.NumGoroutine(),
runtime.NumGoroutine(), // 近似値として使用
runtime.GOMAXPROCS(0))
case <-done:
return
}
}
}
// 1. CPU集約的なワークロード
go func() {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < 100000000; j++ {
_ = j * j
}
}()
}
wg.Wait()
}()
monitorThreads("CPU集約的ワークロード", 2*time.Second)
// 2. I/O集約的なワークロード
go func() {
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
defer wg.Done()
time.Sleep(1 * time.Second)
}()
}
wg.Wait()
}()
monitorThreads("I/O集約的ワークロード", 2*time.Second)
// 3. ブロッキングシステムコール
go func() {
var wg sync.WaitGroup
for i := 0; i < 20; i++ {
wg.Add(1)
go func() {
defer wg.Done()
// ファイルI/O(ブロッキング)
data := make([]byte, 1024)
for j := 0; j < 10; j++ {
file, err := os.CreateTemp("", "test")
if err == nil {
file.Write(data)
file.Close()
os.Remove(file.Name())
}
time.Sleep(100 * time.Millisecond)
}
}()
}
wg.Wait()
}()
monitorThreads("ブロッキングI/O", 3*time.Second)
}
パフォーマンス最適化の実例
アプリケーション別の最適設定
func demonstrateOptimizationExamples() {
fmt.Println("アプリケーション別GOMAXPROCS最適化:")
// 1. Webサーバー型アプリケーション
webServerOptimization := func() {
fmt.Println("\n1. Webサーバー型アプリケーション:")
// リクエスト処理のシミュレーション
handleRequest := func(requestID int, responseTime time.Duration) {
// データベースアクセスのシミュレーション
time.Sleep(responseTime)
// CPUでの処理
result := 0
for i := 0; i < 100000; i++ {
result += i
}
}
testWebServer := func(gomaxprocs int, concurrent int) time.Duration {
oldProcs := runtime.GOMAXPROCS(gomaxprocs)
defer runtime.GOMAXPROCS(oldProcs)
start := time.Now()
var wg sync.WaitGroup
for i := 0; i < concurrent; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
handleRequest(id, 50*time.Millisecond) // 50ms のI/O
}(i)
}
wg.Wait()
return time.Since(start)
}
concurrentRequests := 100
numCPU := runtime.NumCPU()
for _, procs := range []int{1, numCPU, numCPU * 2} {
duration := testWebServer(procs, concurrentRequests)
fmt.Printf(" GOMAXPROCS=%d: %v (%d concurrent requests)\n",
procs, duration, concurrentRequests)
}
fmt.Println(" 推奨: GOMAXPROCS=CPU数(I/O待機中に他のリクエストを処理)")
}
// 2. 計算集約型アプリケーション
computeIntensiveOptimization := func() {
fmt.Println("\n2. 計算集約型アプリケーション:")
// 大きな行列の乗算シミュレーション
matrixMultiply := func(size int) {
a := make([][]float64, size)
b := make([][]float64, size)
c := make([][]float64, size)
for i := range a {
a[i] = make([]float64, size)
b[i] = make([]float64, size)
c[i] = make([]float64, size)
for j := range a[i] {
a[i][j] = rand.Float64()
b[i][j] = rand.Float64()
}
}
// 行列乗算
for i := 0; i < size; i++ {
for j := 0; j < size; j++ {
for k := 0; k < size; k++ {
c[i][j] += a[i][k] * b[k][j]
}
}
}
}
testComputeIntensive := func(gomaxprocs int) time.Duration {
oldProcs := runtime.GOMAXPROCS(gomaxprocs)
defer runtime.GOMAXPROCS(oldProcs)
start := time.Now()
var wg sync.WaitGroup
numTasks := runtime.NumCPU()
for i := 0; i < numTasks; i++ {
wg.Add(1)
go func() {
defer wg.Done()
matrixMultiply(100) // 100x100 行列
}()
}
wg.Wait()
return time.Since(start)
}
numCPU := runtime.NumCPU()
for _, procs := range []int{1, numCPU/2, numCPU, numCPU * 2} {
if procs <= 0 {
continue
}
duration := testComputeIntensive(procs)
fmt.Printf(" GOMAXPROCS=%d: %v\n", procs, duration)
}
fmt.Println(" 推奨: GOMAXPROCS=CPU数(CPU集約的な処理)")
}
// 3. バッチ処理アプリケーション
batchProcessingOptimization := func() {
fmt.Println("\n3. バッチ処理アプリケーション:")
processData := func(data []int) int {
sum := 0
for _, v := range data {
// 複雑な処理のシミュレーション
for i := 0; i < 1000; i++ {
sum += v * i
}
}
return sum
}
testBatchProcessing := func(gomaxprocs int) time.Duration {
oldProcs := runtime.GOMAXPROCS(gomaxprocs)
defer runtime.GOMAXPROCS(oldProcs)
start := time.Now()
var wg sync.WaitGroup
// 大量のデータを分割して処理
totalData := 10000
chunkSize := totalData / gomaxprocs
for i := 0; i < gomaxprocs; i++ {
wg.Add(1)
go func(start, end int) {
defer wg.Done()
chunk := make([]int, end-start)
for j := range chunk {
chunk[j] = start + j
}
_ = processData(chunk)
}(i*chunkSize, (i+1)*chunkSize)
}
wg.Wait()
return time.Since(start)
}
numCPU := runtime.NumCPU()
for _, procs := range []int{1, numCPU/2, numCPU, numCPU * 2} {
if procs <= 0 {
continue
}
duration := testBatchProcessing(procs)
fmt.Printf(" GOMAXPROCS=%d: %v\n", procs, duration)
}
fmt.Println(" 推奨: GOMAXPROCS=CPU数(データ並列処理)")
}
webServerOptimization()
computeIntensiveOptimization()
batchProcessingOptimization()
}
動的な GOMAXPROCS 調整
実行時の動的調整
func demonstrateDynamicAdjustment() {
fmt.Println("動的なGOMAXPROCS調整:")
// システム負荷に基づく調整
type LoadMonitor struct {
mu sync.RWMutex
cpuUsage float64
goroutines int
optimalProcs int
}
func NewLoadMonitor() *LoadMonitor {
return &LoadMonitor{
optimalProcs: runtime.NumCPU(),
}
}
func (lm *LoadMonitor) UpdateMetrics() {
lm.mu.Lock()
defer lm.mu.Unlock()
// 簡単なメトリクス収集(実際にはより詳細な監視が必要)
lm.goroutines = runtime.NumGoroutine()
// CPU使用率のシミュレーション(実際はシステムメトリクスを使用)
lm.cpuUsage = rand.Float64() * 100
}
func (lm *LoadMonitor) AdjustGOMAXPROCS() {
lm.mu.RLock()
defer lm.mu.RUnlock()
current := runtime.GOMAXPROCS(0)
newProcs := current
// 簡単な調整ロジック
if lm.cpuUsage > 80 && lm.goroutines > current*10 {
// 高CPU使用率 + 多数のgoroutine → 削減
newProcs = max(1, current-1)
} else if lm.cpuUsage < 50 && lm.goroutines < current*5 {
// 低CPU使用率 + 少数のgoroutine → 増加
newProcs = min(runtime.NumCPU(), current+1)
}
if newProcs != current {
runtime.GOMAXPROCS(newProcs)
fmt.Printf("GOMAXPROCS adjusted: %d -> %d (CPU: %.1f%%, Goroutines: %d)\n",
current, newProcs, lm.cpuUsage, lm.goroutines)
}
}
monitor := NewLoadMonitor()
// 動的調整のシミュレーション
go func() {
ticker := time.NewTicker(500 * time.Millisecond)
defer ticker.Stop()
for i := 0; i < 10; i++ {
<-ticker.C
monitor.UpdateMetrics()
monitor.AdjustGOMAXPROCS()
}
}()
// 変動するワークロードの生成
var wg sync.WaitGroup
for phase := 0; phase < 3; phase++ {
fmt.Printf("\nPhase %d: ", phase+1)
var numGoroutines int
var workIntensity int
switch phase {
case 0:
fmt.Println("軽いワークロード")
numGoroutines = 5
workIntensity = 1000000
case 1:
fmt.Println("重いワークロード")
numGoroutines = 20
workIntensity = 10000000
case 2:
fmt.Println("中程度のワークロード")
numGoroutines = 10
workIntensity = 5000000
}
for i := 0; i < numGoroutines; i++ {
wg.Add(1)
go func(intensity int) {
defer wg.Done()
sum := 0
for j := 0; j < intensity; j++ {
sum += j
}
_ = sum
}(workIntensity)
}
time.Sleep(2 * time.Second)
}
wg.Wait()
fmt.Printf("\n最終GOMAXPROCS: %d\n", runtime.GOMAXPROCS(0))
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
ベストプラクティス
GOMAXPROCS設定の指針
func demonstrateBestPractices() {
fmt.Println("GOMAXPROCS設定のベストプラクティス:")
practices := []struct {
scenario string
setting string
reason string
}{
{
"CPU集約的アプリケーション",
"GOMAXPROCS = CPU数",
"各CPUコアで並列実行し最大性能を発揮",
},
{
"I/O集約的アプリケーション",
"GOMAXPROCS = CPU数(デフォルト)",
"I/O待機中に他のgoroutineが実行される",
},
{
"混合ワークロード",
"GOMAXPROCS = CPU数",
"バランスの取れた設定として適切",
},
{
"コンテナ環境",
"CPUクォータに基づく設定",
"利用可能リソースに合わせた制限",
},
{
"デバッグ・開発",
"GOMAXPROCS = 1",
"並行性問題を特定しやすくする",
},
{
"レガシーシステム",
"段階的に増加",
"既存システムへの影響を最小化",
},
}
for _, practice := range practices {
fmt.Printf("\n%s:\n", practice.scenario)
fmt.Printf(" 設定: %s\n", practice.setting)
fmt.Printf(" 理由: %s\n", practice.reason)
}
fmt.Println("\n一般的な注意点:")
warnings := []string{
"• CPU数より大幅に多く設定しても効果なし",
"• 1に設定すると並列性が完全に無効化",
"• コンテナでは利用可能CPU数を正確に把握",
"• ベンチマークで実際の効果を測定",
"• アプリケーションの特性を理解してから設定",
}
for _, warning := range warnings {
fmt.Println(warning)
}
// 現在の推奨設定
fmt.Printf("\n現在の環境での推奨設定:\n")
fmt.Printf(" CPU数: %d\n", runtime.NumCPU())
fmt.Printf(" 現在のGOMAXPROCS: %d\n", runtime.GOMAXPROCS(0))
fmt.Printf(" 推奨設定: GOMAXPROCS=%d (デフォルトのまま)\n", runtime.NumCPU())
}
Go言語におけるGOMAXPROCS
の制御は、アプリケーションの性質を理解した上で適切に設定することが重要です。多くの場合、デフォルト値(CPU数と同じ)が最適ですが、特定の要件や制約がある場合は調整を検討する価値があります。
おわりに
本日は、Go言語のよくある質問について解説しました。

何か質問や相談があれば、コメントをお願いします。また、エンジニア案件の相談にも随時対応していますので、お気軽にお問い合わせください。
それでは、また明日お会いしましょう(^^)
コメント