1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Go言語】並行処理の基礎とREST API開発のコツ 〜goroutineとchannelで一歩上のGopherへ〜

1
Posted at

はじめに:Goを学び始めたけれど、この先が見えないあなたへ

「Goの基本文法は一通り理解した。簡単なCLIツールも作れるようになった。でも、そこから先どう進めばいいんだろう?」

Go言語を学習しているエンジニアの多くが、この壁にぶつかります。特に、Goの真骨頂とも言える 並行処理(Concurrency) と、実務で頻出する REST API開発 については、「なんとなくわかるけど、自信を持って書けない」という方が非常に多い領域です。

  • goroutine を起動したはいいけれど、いつ終わるのか制御できない
  • channel の使い方がイマイチ腑に落ちない
  • REST APIを書いてみたけれど、エラーハンドリングやテストが雑になってしまう

この記事では、こうした悩みを解消するために Goの並行処理の基礎REST API開発の実践的なコツ を、コード例を交えながら解説していきます。読み終わる頃には、「なるほど、Goらしい書き方ってこういうことか」と一段深く理解できているはずです。


1. Goの並行処理:goroutineとchannelを正しく使う

1-1. goroutineは「軽量スレッド」

Goの並行処理の主役は goroutine です。go キーワードを関数呼び出しの前に付けるだけで、その関数を並行に実行できます。

package main

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

func worker(id int, wg *sync.WaitGroup) {
    defer wg.Done() // 関数終了時にカウンタを減らす
    fmt.Printf("Worker %d 開始\n", id)
    time.Sleep(time.Second)
    fmt.Printf("Worker %d 完了\n", id)
}

func main() {
    var wg sync.WaitGroup
    for i := 1; i <= 3; i++ {
        wg.Add(1)          // カウンタを増やす
        go worker(i, &wg)  // goroutineを起動
    }
    wg.Wait() // すべてのgoroutineが終わるまで待つ
    fmt.Println("すべての処理が完了しました")
}

ここでのポイントは sync.WaitGroup です。goroutine は起動したそばから独立して動くため、明示的に「終わるのを待つ」仕組みが必要になります。これを忘れると、main 関数が先に終了してしまい、goroutine の処理が途中で切れてしまいます。

1-2. channelでデータをやり取りする

Goの哲学に「Don't communicate by sharing memory; share memory by communicating.(メモリを共有して通信するのではなく、通信によってメモリを共有せよ)」という有名な言葉があります。この哲学を体現するのが channel です。

func main() {
    ch := make(chan int, 3) // バッファ付きチャネル

    go func() {
        for i := 1; i <= 3; i++ {
            ch <- i * 10 // チャネルに送信
        }
        close(ch) // 送信終了を通知
    }()

    for v := range ch { // チャネルから受信
        fmt.Println("受信:", v)
    }
}

1-3. context でキャンセル制御を行う

実務では「タイムアウトしたら処理を打ち切る」「親の処理が中断されたら子も止める」といった キャンセル制御 が不可欠です。ここで登場するのが context パッケージです。

ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()

select {
case <-time.After(3 * time.Second):
    fmt.Println("処理完了")
case <-ctx.Done():
    fmt.Println("タイムアウト:", ctx.Err())
}

context は特にREST APIやDB接続で頻繁に使う機能なので、必ず押さえておきましょう。


2. REST API開発のコツ:標準ライブラリで十分書ける

2-1. まずは net/http を理解する

GoにはGinやEchoなどの有名なWebフレームワークがありますが、まずは標準ライブラリ net/http を使いこなせるようになる ことが上達の近道です。

package main

import (
    "encoding/json"
    "log"
    "net/http"
)

type User struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
}

func getUser(w http.ResponseWriter, r *http.Request) {
    if r.Method != http.MethodGet {
        http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
        return
    }
    user := User{ID: 1, Name: "Taro"}
    w.Header().Set("Content-Type", "application/json")
    if err := json.NewEncoder(w).Encode(user); err != nil {
        http.Error(w, "内部エラー", http.StatusInternalServerError)
        return
    }
}

func main() {
    http.HandleFunc("/user", getUser)
    log.Println("Server started at :8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

2-2. ハンドラー設計で意識したい3つのポイント

REST APIを書くとき、以下の3点を意識するとコード品質が一気に上がります。

  • 単一責任にする:1つのハンドラーは1つのユースケースに絞る
  • エラーは早期リターンで処理:ネストを深くしない
  • context をハンドラーの引数として引き回すr.Context() を使い、DB処理などに渡す

2-3. 並行処理を活かした外部API呼び出し

REST APIの中で複数の外部APIを叩く場面はよくあります。ここでこそ goroutine の出番です。

func fetchAll(ctx context.Context) (Result, error) {
    var wg sync.WaitGroup
    var user User
    var orders []Order
    var errUser, errOrders error

    wg.Add(2)
    go func() {
        defer wg.Done()
        user, errUser = fetchUser(ctx)
    }()
    go func() {
        defer wg.Done()
        orders, errOrders = fetchOrders(ctx)
    }()
    wg.Wait()

    if errUser != nil { return Result{}, errUser }
    if errOrders != nil { return Result{}, errOrders }
    return Result{User: user, Orders: orders}, nil
}

順次実行なら合計 A + B 秒かかる処理も、並行化すれば max(A, B) 秒で済みます。レスポンス速度が劇的に改善する のは、Goが実務で選ばれる大きな理由の一つです。

より高度なパターンとしては、errgroup.Group を使うとエラーハンドリングとキャンセル制御をよりスマートに書けます。


3. まとめ:Goの中級への道筋

ここまで解説した内容を振り返っておきましょう。

  • goroutine と WaitGroup で並行処理の基本を制御する
  • channel でgoroutine間の安全なデータ受け渡しを行う
  • context でタイムアウト・キャンセルを扱う
  • REST APIは net/http の理解が土台 になる
  • 外部API呼び出しなどI/O待ちの処理は並行化 することで大きな価値を生む

Goの並行処理とREST API開発は、この2つが組み合わさって初めて真価を発揮します。「文法はわかる」から「実務で使える」へ、そのステップアップの鍵はまさにここにあるのです。


次のステップ:手を動かしながら体系的に学びたい方へ

とはいえ、記事を読むだけでは「わかったつもり」で終わってしまうのも事実です。並行処理やREST APIは、実際に自分で書いて、動かして、失敗してみる ことで初めて身につきます。

もし「もっとドリル形式で数をこなしながら実力を固めたい」「テストコードまで含めて実践レベルに引き上げたい」という方には、次の講座が学習リソースの一つとして役立つかもしれません。

📘 Go言語中級:30問ドリルで学ぶ並行処理・REST API・テスト実践講座

  • 30問のドリル形式 で並行処理・REST API・テストを段階的に習得
  • 実務でつまずきやすいポイントを、手を動かしながら克服できる
  • 中級者に必要な「テストを書く習慣」まで一気にキャッチアップ

書籍や公式ドキュメントで基礎を押さえたあとの アウトプット教材 としてちょうどよい構成になっています。「読むだけでは物足りない」というフェーズに来ている方は、ぜひ選択肢の一つとして覗いてみてください。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?