⚙️

Go言語で学ぶ並行性と並列性

2024/04/20に公開

並行性と並列性

Go言語におけるgoroutineは、軽量なスレッドのようなもので、Goのランタイムによって管理されます。
goroutineを使用することで、非常に簡単に並行性(Concurrency)と並列性(Parallelism)を実現することができます。
この記事では、並行性と並列性の基本的な違いと、それらをGoでどのように実装するかをサンプルコードとともに説明します。

並行性(Concurrency)

並行性は、複数のタスクが交互に実行されることを指します。
これは、シングルコアプロセッサ上でも実現できます。
並行性を利用することで、I/O操作などの待ち時間を有効に活用し、全体の処理時間を短縮することができます。

以下のサンプルコードは、並行性を示すものです。数字と英字を交互に印刷する2つのタスクを並行して実行します。

package main

import (
	"fmt"
	"sync"
	"time"
)

func main() {
	var wg sync.WaitGroup
	wg.Add(2) // 2つのタスクを待つ

	// 数字を印刷
	go func() {
		for i := 1; i <= 5; i++ {
			time.Sleep(100 * time.Millisecond) // タスクの切り替えを模擬
			fmt.Printf("%d ", i)
		}
		wg.Done()
	}()

	// 英字を印刷
	go func() {
		for i := 'a'; i <= 'e'; i++ {
			time.Sleep(100 * time.Millisecond) // タスクの切り替えを模擬
			fmt.Printf("%c ", i)
		}
		wg.Done()
	}()

	wg.Wait() // 2つのgoroutineが完了するのを待つ
	fmt.Println("\n並行性デモ完了")
}

並列性(Parallelism)

並列性は、複数のタスクが同時に実行されることを指します。
これは、マルチコアプロセッサを使用して実現されます。
並列性を利用することで、タスクの完了時間を大幅に短縮することができます。

以下のサンプルコードは、並列性を示すものです。数字と英字を同時に印刷する2つのタスクを並列して実行します。

package main

import (
	"fmt"
	"sync"
)

func main() {
	var wg sync.WaitGroup
	wg.Add(2) // 2つのタスクを待つ

	// 数字を印刷
	go func() {
		for i := 1; i <= 5; i++ {
			fmt.Printf("%d ", i)
		}
		wg.Done()
	}()

	// 英字を印刷
	go func() {
		for i := 'a'; i <= 'e'; i++ {
			fmt.Printf("%c ", i)
		}
		wg.Done()
	}()

	wg.Wait() // 2つのgoroutineが完了するのを待つ
	fmt.Println("\n並列性デモ完了")
}

この記事では、Go言語における並行性と並列性の基本的な概念とその違いについて説明しました。
並行性は、複数のタスクが交互に実行されることであり、一方で並列性は、複数のタスクが文字通り同時に実行されることです。
Go言語のgoroutineを使用することで、これらの概念を簡単に実装することができます。

並行性は、シングルコアプロセッサでも実現できますが、並列性を実現するにはマルチコアプロセッサが必要です。
この違いは、プログラムのパフォーマンスに大きな影響を与えるため、開発者は適切なアプローチを選択する必要があります。

Go言語では、sync.WaitGroupを使用して、複数のgoroutineの完了を同期的に待つことができます。
これにより、プログラムの実行フローを簡単に管理することができます。

最後に、並行性と並列性を適切に利用することで、プログラムの効率を大幅に向上させることができます。
しかし、これらの概念を適用する際には、データ競合やデッドロックなどの問題に注意する必要があります。
適切な同期メカニズムを使用することで、これらの問題を回避することができます。

この記事を通じて、Go言語における並行性と並列性の基本を理解し、それらを効果的に活用する方法を学びました。
これらの概念を自分のプロジェクトに適用することで、より高速で効率的なプログラムを開発することができるでしょう。

実は、、、

実は、、、 ここまでの内容はすべて ChatGPT に書かせてみました。
自分、Hello World すら叩いたことない状態なのでした。(go sample.go と叩いて動かないじゃん!って思うレベル)
その状況で goroutine が AWS Lambda の並行処理をどこまで性能出せるのか?という課題にぶちあたり今回ブログに書いてしまおうと思いあたりました。

実際にサンプル動かしてみた

まず ChatGPT が提示したコードを実行してみます。

並行処理

❯ go run concurrency.go 
1 a 2 b c 3 4 d e 5 
並行性デモ完了

並列処理

❯ go run parallelism.go 
a b c d e 1 2 3 4 5 
並列性デモ完了

並列処理の方が頭おかしいくらい早く終わりました。(ただコードを見ると Sleep の有無くらいしか違いがない、、、)
並行処理は1つの脳で交互にマルチタスクをこなしてるのに対して並列処理は、複数の脳で同時に処理しているので当然と言えは当然です。

早いのとトレードオフで逆に順番とかを意識するとなると並列処理はどうなるんだろうと思ったりはしました。
ただ実際には並行処理は実行するたびに実行結果が違うが並列処理は毎回同じです。
恐らくこのあたりは Sleep を入れたことでタスク切り替えを Go がよしなにやってくれているのだと思いました。
(逆に並列ちゃんと動いているのか?という疑問は残りますが、、、)

思うこと

Go で並行性、並列性の処理を行う場合

  • goroutine を使えばよしなに処理を切り替えてくれる
  • WaitGroup を使って処理の完了待ちは制御する
  • IO とかのブロッキングとかは自分で考えないといけないのかな?

あたりは今回の学びなのだと思いました。

GitHubで編集を提案

Discussion