この記事では、今話題のプログラミング言語、Go言語のゴルーチン(goroutine)について初心者にも分かるように、基本から使い方、注意点まで解説していきます!
ゴルーチンって聞くと、「なんか難しそう…」「並行処理って何?」って思うかもしれませんね。
この記事を読めば、ゴルーチンの魅力と、意外なほど簡単な使い方にきっと驚くはずです。さあ、一緒にGo言語のパワフルな世界を覗いてみましょう!
この記事で学べること
- ゴルーチンが何なのか、基本的な考え方
- ゴルーチンを使うと、どんないいことがあるのか
- ゴルーチンの超簡単な書き方
- 実際にゴルーチンを動かすサンプルコードと解説
- ゴルーチンを使うときに気をつけること
Go言語のゴルーチンとは?
さて、さっそくですが「ゴルーチン(goroutine)」って一体何者なんでしょう?
簡単に言うと、Go言語が用意してくれた、処理を同時にたくさん動かすための軽い仕組みのことです。
「同時にたくさん動かす」って、ピンとこないかもしれませんね。
例えば、あなたが料理をしているとしましょう。野菜を切りながら、隣のコンロでお湯を沸かす、みたいな作業を同時に進めますよね?
プログラムの世界でも、これと同じように複数の処理を並行して進めたい場面がたくさんあります。ウェブサーバーなら、たくさんの人からのアクセスを同時にさばく必要がありますし。
ゴルーチンは、こういう「並行処理」を、Go言語では驚くほど簡単に、そして効率的に実現するための機能なんです。
他のプログラミング言語にも似たような機能(スレッドとか)はあるんですが、Go言語のゴルーチンはもっと手軽で、少ない負担でたくさんの処理を動かせるのが特徴です。だからGo言語が人気な理由の一つにもなっています。
なぜゴルーチンを使うの?メリットを解説
じゃあ、ゴルーチンを使うと、具体的にどんないいことがあるんでしょうか? 主なメリットはこんな感じです。
プログラムの反応が良くなる
時間のかかる処理をゴルーチンに任せておけば、その間も他の処理を進められるので、ユーザーを待たせにくくなります。例えば、重いファイルの読み込み中でも、画面はサクサク動く、みたいな感じです。コンピューターの力を無駄なく使える
今のコンピューターは、たくさんの仕事を同時にこなせる力を持っています(マルチコアCPUとか言いますね)。ゴルーチンを使うと、その力を効率よく引き出して、プログラム全体の処理速度を上げることが期待できます。コードが分かりやすくなる場合がある
処理の流れによっては、一つの大きな処理をいくつかの独立したゴルーチンに分けたほうが、コードの見通しが良くなることがあります。特に、ネットワーク通信やファイルの読み書きみたいに、「待ち時間」が発生しやすい処理でゴルーチンのメリットを感じやすいですよ。
【基本】Go言語のゴルーチンの書き方
メリットが分かったところで、いよいよゴルーチンの書き方です。
「え、これだけ?」って拍子抜けするほど簡単ですよ!
go
キーワードの使い方
Go言語で関数をゴルーチンとして動かすには、その関数呼び出しの前に `go` というキーワードを付けるだけなんです。
本当に、たったこれだけ。
package main import "fmt" func sayHello() { fmt.Println("こんにちは!ゴルーチンより") } func main() { // sayHello関数をゴルーチンとして起動! go sayHello() fmt.Println("main関数よりこんにちは!") // ちょっと待たないと、ゴルーチンが終わる前にmain関数が終わっちゃう // time.Sleep(1 * time.Second) // ← あとで説明します }
上の例だと、`go sayHello()` の部分ですね。
こう書くと、`sayHello`関数は`main`関数とは別の流れ(これがゴルーチン)で実行が始まります。
`go` を付けるだけで、関数がバックグラウンドで動き出すイメージです。簡単でしょ?
【実践】Go言語のゴルーチンを使ってみよう
理屈だけじゃなく、実際に動かしてみるのが一番!
簡単なサンプルコードを用意したので、ぜひ動かしてみてください。
サンプルプログラムの紹介
このプログラムは、二つの異なるメッセージを、それぞれ別のゴルーチンから表示する、というものです。
わざと少し時間がかかるようにして、並行に動いている感じを見てみましょう。
package main import ( "fmt" "time" // 時間を扱うためのパッケージ ) // メッセージを表示する関数 func printMessage(message string, delay time.Duration) { time.Sleep(delay) // 指定された時間だけ待つ fmt.Println(message) } func main() { fmt.Println("プログラム開始!") // 一つ目のゴルーチンを起動 go printMessage("こちらはゴルーチン1からのメッセージです!", 1*time.Second) // 1秒待つ // 二つ目のゴルーチンを起動 go printMessage("こっちはゴルーチン2から!少し遅れて表示されるはず", 2*time.Second) // 2秒待つ fmt.Println("ゴルーチンを起動しました。") // ゴルーチンが終わるのを待つ (今回は適当に3秒待つ) // これがないと、ゴルーチンがメッセージを表示する前にプログラムが終わっちゃう! time.Sleep(3 * time.Second) fmt.Println("プログラム終了!") }
実行結果
このコードを実行すると、コンソールにはだいたいこんな感じで表示されるはずです。
プログラム開始! ゴルーチンを起動しました。 (1秒後くらいに↓が表示される) こちらはゴルーチン1からのメッセージです! (さらに1秒後くらいに↓が表示される) こっちはゴルーチン2から!少し遅れて表示されるはず (さらに1秒後くらいに↓が表示される) プログラム終了!
実行するタイミングによっては、メッセージの表示順が少し前後することもあるかもしれません。
ゴルーチンは基本的に他の処理と並行して動くので、必ずしも書いた順番通りに終わるとは限らないのがポイントです。これが並行処理の面白いところですね!
サンプルプログラムの解説
コードの中身を少し詳しく見ていきましょう。
- `import ("fmt", "time")`
文字を表示するための`fmt`パッケージと、時間を扱うための`time`パッケージを使いますよ、という宣言です。 - `func printMessage(message string, delay time.Duration)`
メッセージ文字列と、待機時間(`time.Duration`型)を受け取って、待機後にメッセージを表示する関数です。 - `time.Sleep(delay)`
この行で、指定された時間だけ処理を一時停止しています。ゴルーチンが並行で動いているのを確認しやすくするために、わざと入れています。 - `go printMessage(...)`
ここが核心部分!`printMessage`関数をゴルーチンとして起動しています。一つ目は1秒、二つ目は2秒待つように指示していますね。`go`キーワードがあるので、これらの関数呼び出しはすぐに完了し、`main`関数の次の行に進みます。関数自体は裏で動き続けます。 - `time.Sleep(3 * time.Second)`
これが結構重要!`main`関数がゴルーチンの処理が終わる前に終了してしまうと、起動したゴルーチンも強制的に止められてしまいます。そのため、ここではゴルーチンが終わりそうな時間(今回は適当に3秒)だけ`main`関数を待たせています。もっとちゃんとした待ち方(チャネルとかを使います)もあるのですが、今回は一番簡単な方法を使ってみました。
Go言語のゴルーチンを使う上での注意点
簡単に使えるゴルーチンですが、いくつか気をつけるべき点があります。特に初心者のうちは、以下の点に注意しましょう。
`main`関数が終わるとゴルーチンも終わる!
さっきのサンプルコードでも触れましたが、プログラム本体である`main`関数が終了すると、たとえ処理の途中であっても、起動したすべてのゴルーチンは強制終了してしまいます。データの受け渡しには工夫が必要
ゴルーチン同士で情報(データ)をやり取りしたい場面はよくあります。例えば、あるゴルーチンが計算した結果を別のゴルーチンが使う、みたいなケースです。複数のゴルーチンから同じデータを書き換えるのは危険!
複数のゴルーチンが、同時に同じ変数などのデータを書き換えようとすると、データが壊れたり、プログラムがおかしな動きをしたりする原因になります。最初は「ふーん、そうなんだ」くらいで大丈夫です。実際にコードを書いていくうちに、「あ、こういうことか!」と分かってきますよ。
ゴルーチンと一緒に使われる「チャネル」とは?
さっきからチラチラ出てきている「チャネル(Channel)」という言葉、気になりますよね。
これは、ゴルーチンを使う上で、切っても切れない相棒のような存在です。
チャネルを一言でいうと、ゴルーチン間で安全にデータを送受信するための「パイプ」や「連絡通路」のようなものです。
ゴルーチンAが「このデータ使って!」とチャネルにデータを送り、ゴルーチンBがそのチャネルからデータを受け取る、といった使い方をします。
百聞は一見にしかず、簡単なサンプルコードを見てみましょう!
package main import ( "fmt" "time" ) // ゴルーチンとして動く関数。チャネルを受け取る func worker(messageChan chan string) { fmt.Println("ゴルーチン: ちょっと時間のかかる作業を開始します...") time.Sleep(2 * time.Second) // 2秒かかる作業のフリ fmt.Println("ゴルーチン: 作業が終わりました!") // 作業結果のメッセージをチャネルに送信! messageChan <- "お待たせ!作業完了の報告です!" } func main() { fmt.Println("プログラム開始") // 文字列を送受信できるチャネルを作成 // chan string は「文字列型のチャネル」という意味 ch := make(chan string) // worker関数をゴルーチンとして起動! 作成したチャネルを渡す go worker(ch) fmt.Println("main: ゴルーチンからの報告を待っています...") // チャネルからメッセージを受信する // ゴルーチンがチャネルに送信するまで、ここで処理が待機される! receivedMessage := <-ch fmt.Println("main: ゴルーチンから報告を受け取りました!") fmt.Println("受け取った内容:", receivedMessage) fmt.Println("プログラム終了") }
このコードを実行すると、まず`main`関数が動き出し、`worker`関数をゴルーチンとして起動します。
そして、`main`関数は `receivedMessage := <-ch b="">チャネル`ch`にデータが送られてくるのを健気に待ちます-ch>。
実行結果(目安)
プログラム開始 main: ゴルーチンからの報告を待っています... ゴルーチン: ちょっと時間のかかる作業を開始します... (2秒後くらいに↓が表示される) ゴルーチン: 作業が終わりました! main: ゴルーチンから報告を受け取りました! 受け取った内容: お待たせ!作業完了の報告です! プログラム終了
このように、チャネルを使うことで、ゴルーチン間の安全なデータの受け渡しと、ゴルーチンの処理が終わるのを待つ(同期)という二つのことが、シンプルに実現できるんです。
さっき注意点で挙げた「`main`関数の待ち合わせ」も、`time.Sleep`のような曖昧な方法ではなく、チャネルを使えばもっと確実に行えますね!
もちろん、これはチャネルの非常に基本的な使い方の一つです。チャネルにはもっと色々な機能や使い方がありますが、まずは「ゴルーチン同士が連絡を取り合うための道具」というイメージを持っておくと良いでしょう。
ゴルーチンとチャネルを組み合わせることで、Go言語の並行処理は真価を発揮します。
【まとめ】Go言語のゴルーチンをマスターしよう!
今回はGo言語のパワフルな機能「ゴルーチン」について、基本的なところから見てきました。
もう一度ポイントをおさらいしましょう。
- ゴルーチンは、Go言語で並行処理を簡単に実現する仕組み
- 関数呼び出しの前に `go` を付けるだけで起動できる
- プログラムの反応を良くしたり、CPUパワーを有効活用できるメリットがある
- `main`関数が終わるとゴルーチンも終わるので注意が必要
- データのやり取りには「チャネル」という仕組みが便利
どうでしょう?「ゴルーチン、意外といけるかも?」と思っていただけたら嬉しいです。
最初は簡単なプログラムでいいので、ぜひ `go` キーワードを使って、ゴルーチンを動かす体験をしてみてください。
実際に手を動かすのが、一番の近道ですからね!
この記事が、あなたのGo言語学習の助けになれば幸いです。どんどんコードを書いて、Go言語の世界を楽しんでいきましょう!
【関連記事】 Go言語とは?特徴・できること・将来性
-ch>->
0 件のコメント:
コメントを投稿
注: コメントを投稿できるのは、このブログのメンバーだけです。