Go言語での並列処理をする6つの方法

初心者向けGo言語並列処理の徹底解説のイメージGo言語
この記事は約12分で読めます。

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

基本的な知識があればサンプルコードを活用して機能追加、目的を達成できるように作ってあります。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を凌駕する現役のプログラマチームによって監修されています。

サイト内のコードを共有する場合は、参照元として引用して下さいますと幸いです

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

プログラミングにおいて、並列処理は効率的なプログラムを作成するための重要な要素です。

特に、Go言語はそのシンプルな構文とパワフルな並列処理の機能で知られています。

この記事では、Go言語を使った並列処理の基本から応用までを、初心者にも理解しやすい形で詳細に解説します。

Go言語の基本的な知識から、並列処理を実装するための具体的なステップまで、丁寧に学ぶことができるでしょう。

●Go言語と並列処理の基本

Go言語はGoogleによって開発されたプログラミング言語で、その設計はシンプルさと高いパフォーマンスを目指しています。

Go言語の最大の特徴の一つは、”ゴルーチン”と呼ばれる軽量スレッドを使った並列処理の容易さです。

これにより、複数のタスクを同時に効率的に処理することが可能になります。

○Go言語とは?

Go言語は、コンパイル言語でありながら、スクリプト言語のような書きやすさを持つのが特徴です。

静的型付けを採用しており、大規模なプログラムの開発にも適しています。

また、Go言語はガベージコレクションを備えているため、メモリ管理が容易です。

これらの特徴により、Go言語はクラウドコンピューティングやマイクロサービスの開発において広く利用されています。

○並列処理とは?

並列処理とは、複数の計算処理を同時に行うことで、プログラムの実行効率を高める手法です。

Go言語では、ゴルーチンを用いて軽量なスレッドを生成し、これによって複数のタスクを並行して実行することができます。

並列処理により、CPUの複数のコアを活用して処理速度を向上させることが可能になります。

Go言語における並列処理の強みは、そのシンプルさと効率の良さにあります。

従来のスレッドを使った並列処理と比較して、ゴルーチンはメモリ消費が少なく、簡単に大量の並列タスクを扱うことができます。

●Go言語の基本的な構文

Go言語は、その簡潔さと効率的な構文で人気を集めています。

初心者でも容易に理解できるよう、Go言語の基本的な構文を解説します。

Go言語の構文は、読みやすさと書きやすさを重視して設計されており、他の言語に比べて学習しやすいのが特徴です。

○変数と型

Go言語では、変数を宣言する際に型を指定します。

型には、整数型(int)、浮動小数点型(float32, float64)、文字列型(string)などがあります。

Go言語の型システムは強力で、明示的な型変換が必要です。これにより、型の不一致によるエラーを防ぐことができます。

例えば、整数型の変数を宣言するには下記のようにします。

var x int = 10

このコードでは、xという名前の整数型変数を宣言し、それに10という値を割り当てています。

○関数とメソッド

Go言語では、関数を定義して特定のタスクを実行することができます。

関数は、入力パラメータと戻り値を持つことができ、プログラムの再利用性と整理を助けます。

例えば、2つの整数を足し合わせる関数は下記のようになります。

func add(a int, b int) int {
    return a + b
}

この関数addは、2つのint型のパラメータabを受け取り、その和をint型で返します。

○制御構造

Go言語には、制御フローを管理するためのいくつかの構造があります。最も基本的なのはif文とfor文です。

if文は条件が真の場合にコードブロックを実行し、for文はループ処理を実行します。

例えば、ある条件下でメッセージを表示するには下記のように書きます。

if x > 5 {
    fmt.Println("x is greater than 5")
}

このコードでは、xが5より大きい場合に、”x is greater than 5″というメッセージが出力されます。

また、繰り返し処理を行うfor文は下記のようになります。

for i := 0; i < 10; i++ {
    fmt.Println(i)
}

この例では、0から9までの数字を順に出力します。

for文は非常に柔軟で、さまざまなループ処理に対応できます。

●Go言語における並列処理の基礎

Go言語での並列処理は、そのパフォーマンスと効率性において非常に注目されています。

Go言語における並列処理の基礎を理解することは、効率的なプログラムを作成する上で重要です。

ここでは、Go言語の並列処理を支える主要な概念であるゴルーチンとチャネルについて解説します。

○ゴルーチン(goroutine)とは

ゴルーチンは、Go言語における軽量スレッドのことで、非常に簡単に作成し使用することができます。

ゴルーチンを使用することで、複数のタスクを並列に実行することが可能となります。

Go言語のランタイムはゴルーチンを非常に効率的にスケジュールし、システムのリソースを最大限に活用します。

ゴルーチンの起動は、goキーワードに続けて関数呼び出しを記述するだけで実現できます

このシンプルさがGo言語の並列処理を容易にします。

○チャネル(channel)の基本

チャネルは、ゴルーチン間でのデータのやり取りを行うためのパイプラインです。

チャネルを使うことで、一方のゴルーチンが別のゴルーチンにデータを送信し、そのデータを受信することができます。

これにより、ゴルーチン間でのデータの同期が可能になります。

チャネルは、make関数を使って作成されます。

チャネルを介したデータの送受信は、<-オペレータを使用して行われます。

○サンプルコード1:シンプルなゴルーチンの使用例

ここでは、ゴルーチンを使った基本的な例を見てみましょう。

下記のサンプルコードは、2つのゴルーチンを起動し、それぞれが異なるメッセージを表示するものです。

package main

import (
    "fmt"
    "time"
)

func printMessage(message string) {
    for i := 0; i < 5; i++ {
        fmt.Println(message)
        time.Sleep(time.Second)
    }
}

func main() {
    go printMessage("Hello from Goroutine 1")
    go printMessage("Hello from Goroutine 2")

    // メインゴルーチンが終了するのを防ぐために待機
    time.Sleep(time.Second * 6)
}

このコードでは、printMessage関数を2つの異なるゴルーチンで並行して実行しています。

各ゴルーチンは、指定されたメッセージを5回出力し、その間1秒間隔で待機します。

メイン関数では、これらのゴルーチンが終了する前にプログラムが終了しないように、6秒間待機することで、並列処理の完了を待っています。

●Go言語での並列処理の応用

Go言語の並列処理の応用は、そのパフォーマンスと効率性をさらに高めるために重要です。

特に、データの同期、処理速度の向上、複数のゴルーチンの管理など、多様なシナリオでの応用が可能です。

ここでは、Go言語での並列処理の応用例をいくつか紹介し、それぞれのサンプルコードを通じて理解を深めます。

○サンプルコード2:チャネルを使ったデータの同期

チャネルを使用することで、ゴルーチン間でデータを安全に送受信することができます。

これにより、並列処理の中で発生するデータの不整合を防ぐことが可能です。

下記のサンプルコードでは、2つのゴルーチン間でチャネルを通じてデータを送受信しています。

package main

import (
    "fmt"
    "time"
)

func sendData(ch chan string) {
    time.Sleep(time.Second * 2)
    ch <- "Data from sendData function"
}

func main() {
    ch := make(chan string)
    go sendData(ch)
    data := <-ch
    fmt.Println(data)
}

このコードでは、sendData関数内で2秒待機した後に、チャネルchを通じて文字列を送信しています。

メインゴルーチンでは、このデータを受信し出力しています。

○サンプルコード3:並列化による処理速度の向上

並列処理の最大の利点の一つは、複数のタスクを同時に実行することで全体の処理速度を向上させることができる点です。

下記のコードは、2つの異なるタスクを同時に実行することで、処理の完了を高速化する例を表しています。

package main

import (
    "fmt"
    "time"
)

func task1() {
    time.Sleep(time.Second * 2)
    fmt.Println("Task 1 completed")
}

func task2() {
    time.Sleep(time.Second * 1)
    fmt.Println("Task 2 completed")
}

func main() {
    go task1()
    go task2()

    time.Sleep(time.Second * 3) // すべてのタスクが完了するのを待機
}

このコードでは、task1task2をゴルーチンとして並行実行しています。

これにより、各タスクは独立して同時に処理され、全体の処理時間が短縮されます。

○サンプルコード4:複数のゴルーチンの管理

大規模なアプリケーションでは、複数のゴルーチンを効果的に管理する必要があります。

下記のサンプルコードは、sync.WaitGroupを使用して、複数のゴルーチンが完了するのを待つ方法を表しています。

package main

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

func worker(id int, wg *sync.WaitGroup) {
    defer wg.Done()
    fmt.Printf("Worker %d starting\n", id)
    time.Sleep(time.Second)
    fmt.Printf("Worker %d done\n", id)
}

func main() {
    var wg sync.WaitGroup
    for i := 1; i <= 5; i++ {
        wg.Add(1)
        go worker(i, &wg)
    }
    wg.Wait() // すべてのゴルーチンが完了するまで待機
}

このコードでは、5つのゴルーチンを起動し、それぞれが独立してタスクを実行しています。

WaitGroupは、すべてのゴルーチンの完了を待機し、アプリケーションの終了を適切に管理します。

●Go言語の並列処理のカスタマイズ方法

Go言語の並列処理は、その基本的な機能だけでなく、カスタマイズを通じてさらに拡張することができます。

特に、ユーザー定義型や外部ライブラリを活用することで、特定のニーズに合わせた高度な並列処理を実現することが可能です。

ここでは、これらのカスタマイズ方法について詳しく解説し、具体的なサンプルコードを通じて理解を深めます。

○サンプルコード5:ユーザー定義型を使った並列処理

Go言語では、ユーザー定義型を使用して、特定のデータ構造を持つ並列処理を実装することができます。

下記のサンプルコードは、ユーザー定義型を用いた並列処理の例を表しています。

package main

import (
    "fmt"
    "sync"
)

type CustomData struct {
    ID    int
    Value string
}

func process(data CustomData, wg *sync.WaitGroup) {
    defer wg.Done()
    fmt.Printf("Processing: %+v\n", data)
}

func main() {
    var wg sync.WaitGroup
    data := []CustomData{
        {ID: 1, Value: "A"},
        {ID: 2, Value: "B"},
        {ID: 3, Value: "C"},
    }

    for _, d := range data {
        wg.Add(1)
        go process(d, &wg)
    }
    wg.Wait()
}

このコードでは、CustomData型を定義し、それを用いて複数のデータを並列に処理しています。

sync.WaitGroupを使用することで、すべての処理が完了するまで待機しています。

○サンプルコード6:外部ライブラリを活用した高度な例

Go言語のエコシステムには、並列処理を助ける多くの外部ライブラリが存在します。

これらのライブラリを活用することで、より高度な並列処理を実装することが可能です。

例えば、特定のタスクキューを管理するためのライブラリを使用することで、並列処理を効率的に管理し、スケールすることができます。

下記のサンプルコードは、外部ライブラリを使用した並列処理の高度な例を表しています。

// ここでは、外部ライブラリの例として仮のコードを提示します。
// 実際のコードは、使用するライブラリに応じて異なります。

package main

import (
    "fmt"
    "github.com/external/library" // 仮の外部ライブラリ
)

func main() {
    taskQueue := library.NewTaskQueue()
    taskQueue.Add(func() {
        fmt.Println("Task 1")
    })
    taskQueue.Add(func() {
        fmt.Println("Task 2")
    })

    taskQueue.Start() // タスクを並列に実行
    taskQueue.Wait()  // すべてのタスクが完了するまで待機
}

このコードは、仮の外部ライブラリを使用してタスクキューを作成し、複数のタスクを並列に実行しています。

実際に使用するライブラリに応じて、具体的な実装方法は異なります。

まとめ

この記事では、Go言語における並列処理の基本から応用、さらにはカスタマイズ方法に至るまでを詳細に解説しました。

ゴルーチンやチャネルの基本的な使い方から、競合状態の回避、メモリ管理、外部ライブラリの活用に至るまで、Go言語における並列処理の効果的な実装方法を幅広くカバーしました。

これらの知識を活用することで、読者はGo言語を使った効率的で安全な並列処理プログラムの開発が可能となるでしょう。