Goroutineとは何か
Go言語の特徴的な機能の一つであるGoroutineは、軽量なスレッドのようなものです。GoroutineはGoのランタイムによって管理され、OSスレッドよりもメモリ消費が少なく、スケジューリングのオーバーヘッドも小さいです。
Goroutineはgo
キーワードを使って関数を呼び出すことで作成されます。この呼び出しは非同期で、呼び出し元の関数とは独立に実行されます。これにより、複数のGoroutineが並行して実行することが可能となり、効率的なマルチタスク処理を実現します。
以下にGoroutineの作成と実行の例を示します。
package main
import (
"fmt"
"time"
)
func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
go say("world")
say("hello")
}
このコードでは、say
関数が2つのGoroutineで実行されます。一つはgo say("world")
で作成され、もう一つはsay("hello")
で作成されます。これらのGoroutineは互いに独立して実行され、出力は互いに交錯して表示されます。
GoroutineはGo言語の強力な機能であり、効率的な並行処理を実現するための重要なツールです。しかし、Goroutineの使用は適切な同期とエラーハンドリングが必要となります。これらのテーマについては、後続のセクションで詳しく説明します。
ForループとGoroutineの組み合わせ
Go言語では、for
ループとgoroutine
を組み合わせることで、複数のタスクを並行に実行することが可能です。これにより、プログラムのパフォーマンスを大幅に向上させることができます。
以下に、for
ループとgoroutine
を組み合わせた例を示します。
package main
import (
"fmt"
"time"
)
func worker(id int, jobs <-chan int, results chan<- int) {
for j := range jobs {
fmt.Println("worker", id, "started job", j)
time.Sleep(time.Second)
fmt.Println("worker", id, "finished job", j)
results <- j * 2
}
}
func main() {
const numJobs = 5
jobs := make(chan int, numJobs)
results := make(chan int, numJobs)
for w := 1; w <= 3; w++ {
go worker(w, jobs, results)
}
for j := 1; j <= numJobs; j++ {
jobs <- j
}
close(jobs)
for a := 1; a <= numJobs; a++ {
<-results
}
}
このコードでは、worker
関数がgoroutine
で実行され、jobs
チャネルからジョブを受け取り、結果をresults
チャネルに送信します。main
関数では、for
ループを使用してジョブをjobs
チャネルに送信し、results
チャネルから結果を受け取ります。
このように、for
ループとgoroutine
を組み合わせることで、複数のタスクを並行に実行し、プログラムのパフォーマンスを向上させることができます。ただし、goroutine
の使用は適切な同期とエラーハンドリングが必要となります。これらのテーマについては、後続のセクションで詳しく説明します。
GoroutineとForループの実用例
Go言語のgoroutine
とfor
ループを組み合わせることで、非常に効率的な並行処理を実現することができます。以下に、Webスクレイピングのタスクを並行化するための実用例を示します。
package main
import (
"fmt"
"net/http"
"time"
)
func fetch(url string, ch chan<- string) {
start := time.Now()
resp, err := http.Get(url)
if err != nil {
ch <- fmt.Sprint(err)
return
}
defer resp.Body.Close()
secs := time.Since(start).Seconds()
ch <- fmt.Sprintf("%.2fs %s", secs, url)
}
func main() {
urls := []string{
"http://example.com",
"http://example.net",
"http://example.org",
// 他のURLを追加...
}
start := time.Now()
ch := make(chan string)
for _, url := range urls {
go fetch(url, ch)
}
for range urls {
fmt.Println(<-ch)
}
fmt.Printf("%.2fs elapsed\n", time.Since(start).Seconds())
}
このコードでは、各URLを非同期にフェッチするためにgoroutine
を使用しています。fetch
関数はgoroutine
で実行され、各URLのフェッチ時間をチャネルに送信します。main
関数では、for
ループを使用してすべてのURLをフェッチし、結果を表示します。
このように、goroutine
とfor
ループを組み合わせることで、複数のWebリクエストを並行に実行し、全体の実行時間を大幅に短縮することができます。ただし、goroutine
の使用は適切な同期とエラーハンドリングが必要となります。これらのテーマについては、後続のセクションで詳しく説明します。
GoroutineとForループの最適な使用法
Go言語のgoroutine
とfor
ループを最適に使用するためには、以下の点を考慮することが重要です。
-
適切な同期:
goroutine
は非同期に実行されるため、データの競合状態を防ぐために適切な同期が必要です。Go言語では、sync
パッケージのWaitGroup
やMutex
、またはチャネルを使用して同期を行うことができます。 -
エラーハンドリング:
goroutine
は呼び出し元とは独立に実行されるため、エラーを適切にハンドリングする必要があります。エラーをチャネルに送信することで、goroutine
のエラーを呼び出し元で処理することができます。 -
リソースの管理: 大量の
goroutine
を生成すると、システムのリソースを過剰に消費する可能性があります。for
ループ内でgoroutine
を生成する場合は、生成するgoroutine
の数を制限するなど、リソースの管理を行うことが重要です。
以下に、これらの点を考慮したコードの例を示します。
package main
import (
"fmt"
"sync"
)
func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
for j := range jobs {
fmt.Println("worker", id, "processing job", j)
results <- j * 2
}
}
func main() {
const numJobs = 100
jobs := make(chan int, numJobs)
results := make(chan int, numJobs)
var wg sync.WaitGroup
for w := 1; w <= 3; w++ {
wg.Add(1)
go worker(w, jobs, results, &wg)
}
for j := 1; j <= numJobs; j++ {
jobs <- j
}
close(jobs)
wg.Wait()
for a := 1; a <= numJobs; a++ {
<-results
}
}
このコードでは、WaitGroup
を使用してgoroutine
の終了を待ち、for
ループで生成するgoroutine
の数を制限しています。これにより、goroutine
とfor
ループを効率的かつ安全に使用することができます。これらのテクニックを理解し、適切に使用することで、Go言語の並行処理の力を最大限に引き出すことができます。