Skip to content

✍️ 필사 모드: Go 完全ガイド — Goroutine・Channel・Context・実戦マイクロサービスまで (Season 2 Ep 3, 2025)

日本語
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.

はじめに — なぜGoは今も使われるのか

Rustが「学習は難しいが強力」だとすれば、Goは「学びやすく、すぐ本番投入できる」側だ。

2024〜2025のGoの位置:

  • Kubernetes: クラウドネイティブの中心。Go製
  • Docker: コンテナ生態系の原点
  • Terraform: IaCの標準
  • Prometheus / Grafana: Observabilityの標準
  • CockroachDB / InfluxDB / etcd: 分散DB
  • gRPC: GoogleのRPC標準、Goファースト
  • Cloudflare, Uber, Monzo, Square: マイクロサービス主力言語
  • Go 1.23 (2024/08): range over func イテレータ、改善されたtimer
  • Go 1.24 (2025/02): Generic Type Alias、tool directive

Goは「Kubernetes世界の共通語」。DevOps・SRE・Cloud Nativeエンジニアには必須。


第1部 — Goの哲学: Less is Moreの真意

1.1 Goが意図的に外したもの

  • Generics (1.18まで無し) — 意図的に遅く導入
  • 三項演算子 — if-elseに統一
  • 例外(try/catch) — Error as value
  • 継承 — Composition only
  • Macro — コード生成ツールで代替
  • 関数オーバーロード — 名前を変える

Rob Pike曰く: 「GoはJavaが多すぎると感じる人のための言語だ。」

1.2 設計原則5つ

  1. Simplicity: 仕様書がポケットに収まる
  2. Composition over Inheritance: interface + embedding
  3. Explicit over Implicit: magicなし
  4. Concurrency as First-class: Goroutine・Channelが言語レベル
  5. Pragmatism: 純粋さより現場性

1.3 得意 vs 苦手

得意苦手
マイクロサービス低レベルメモリ制御
CLIツールGPUコンピューティング
ネットワークプログラムリアルタイムシステム
DevOpsツールゲームエンジン
gRPCサービスモバイルUI
コンテナ/オーケストレーション組み込みファームウェア

第2部 — Goroutine: Goの並行モデル

2.1 Goroutineとは

軽量スレッド。OSスレッド1本で数千のGoroutineをスケジューリング。

go func() {
    fmt.Println("hello from goroutine")
}()
  • OSスレッド: 約2MBスタック
  • Goroutine: 起動時 2KBスタック、必要に応じ動的拡張
  • GOMAXPROCS (既定はCPUコア数) 分のOSスレッドを使用

2.2 M:N スケジューラ

  • M: OSスレッド (Machine)
  • N: Goroutine
  • P: Processor (Goroutineキュー管理)

GoランタイムがM個のGoroutineをN本のスレッド上にマルチプレクス。構造的にはTokioと類似。

2.3 Goroutineリーク — 最も多い本番バグ

// 誤り: receiverが居ないとsenderは永久に待つ
func leak() {
    ch := make(chan int)
    go func() {
        ch <- 42 // 受信者なし → 永久ブロック
    }()
    // 関数は終了、しかしgoroutineは生き続ける
}

対策パターン:

  • context.Contextでキャンセル伝播
  • select<-ctx.Done()を含める
  • Buffered channel + timeout

2.4 Goroutine制御パターン3つ

// 1. WaitGroup — N個の完了待ち
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
    wg.Add(1)
    go func(id int) {
        defer wg.Done()
        // work
    }(i)
}
wg.Wait()

// 2. errgroup — エラー伝播 + キャンセル
import "golang.org/x/sync/errgroup"
g, ctx := errgroup.WithContext(ctx)
for _, url := range urls {
    url := url
    g.Go(func() error {
        return fetch(ctx, url)
    })
}
if err := g.Wait(); err != nil { /* ... */ }

// 3. Semaphore — 同時実行数の制限
import "golang.org/x/sync/semaphore"
sem := semaphore.NewWeighted(10)
for _, task := range tasks {
    task := task
    sem.Acquire(ctx, 1)
    go func() {
        defer sem.Release(1)
        do(task)
    }()
}

第3部 — Channel: メモリを共有して通信するな、通信で共有せよ

3.1 基本

ch := make(chan int)      // Unbuffered
ch := make(chan int, 10)  // Buffered

ch <- 42           // 送信
val := <-ch        // 受信
close(ch)          // クローズ
val, ok := <-ch    // ok=falseなら閉じて空

for v := range ch { // 閉じるまで反復
    fmt.Println(v)
}

3.2 Unbuffered vs Buffered

  • Unbuffered: 送信者と受信者が同期(Rendezvous)
  • Buffered: バッファが埋まるまで送信者はブロックしない

3.3 select — マルチプレクス

select {
case msg := <-ch1:
    fmt.Println("from ch1", msg)
case msg := <-ch2:
    fmt.Println("from ch2", msg)
case <-time.After(1 * time.Second):
    fmt.Println("timeout")
case <-ctx.Done():
    fmt.Println("canceled")
}

selectこそGo並行の真の武器。Channelを組み合わせ複雑なパターンを構築する。

3.4 主要Channelパターン5つ

  1. Fan-out: 1つのChannelを複数Goroutineが消費
  2. Fan-in: 複数Channelを1つに合流
  3. Pipeline: 段階的Channelチェーン
  4. Worker Pool: N個のWorker + Job Channel
  5. Done Channel: キャンセル信号(Contextの前身)
// Worker Pool
jobs := make(chan Job, 100)
results := make(chan Result, 100)

for w := 1; w <= 10; w++ {
    go worker(jobs, results)
}

for _, j := range allJobs {
    jobs <- j
}
close(jobs)

for a := 1; a <= len(allJobs); a++ {
    <-results
}

3.5 Channelの罠3つ

  1. クローズ済Channelへ書き込み: panic。sync.Onceで1回きり保証
  2. nil Channelへの送受信: 永久ブロック。selectで無効化トリックに使う
  3. Over-synchronization: ChannelがMutexより常に良いわけではない

第4部 — Context: Goエコシステムの血流

4.1 Contextとは

リクエスト範囲のキャンセル・デッドライン・値を伝播する標準インターフェース

type Context interface {
    Deadline() (deadline time.Time, ok bool)
    Done() <-chan struct{}
    Err() error
    Value(key any) any
}

4.2 4つの生成関数

ctx := context.Background()            // ルート
ctx := context.TODO()                   // 後で決める
ctx, cancel := context.WithCancel(parent)
ctx, cancel := context.WithTimeout(parent, 5*time.Second)
ctx, cancel := context.WithDeadline(parent, time.Now().Add(1*time.Minute))
ctx := context.WithValue(parent, key, val)

必ずdefer cancel() — リソースリーク防止。

4.3 Context伝播の原則5つ

  1. Contextは常に第一引数: func DoWork(ctx context.Context, arg T) error
  2. Structに保持しない: リクエスト毎に新規生成
  3. nilを渡さない: 曖昧ならcontext.TODO()
  4. Valueはリクエストメタデータ専用: request ID, user ID, trace情報。業務パラメータは不可
  5. 全てのブロッキング呼び出しがctxを受ける: DB, HTTP, gRPC すべて

4.4 Context Valueアンチパターン

// 悪い例: 業務パラメータをValueで渡す
ctx = context.WithValue(ctx, "userID", uid)

// 良い例: 型安全なkey
type ctxKey string
const userIDKey ctxKey = "userID"

ctx = context.WithValue(ctx, userIDKey, uid)
func GetUserID(ctx context.Context) (string, bool) {
    uid, ok := ctx.Value(userIDKey).(string)
    return uid, ok
}

第5部 — エラーハンドリング: Error as Value

5.1 哲学

result, err := doSomething()
if err != nil {
    return nil, fmt.Errorf("doSomething failed: %w", err)
}

例外なし。エラーは値。 明示的に処理するか伝播する。

5.2 Error Wrapping (Go 1.13+)

if err != nil {
    return fmt.Errorf("failed to load user %d: %w", id, err)
}

var notFoundErr *NotFoundError
if errors.As(err, &notFoundErr) { /* ... */ }
if errors.Is(err, sql.ErrNoRows) { /* ... */ }
  • %w: wrap (アンラップ可)
  • %v: 文字列フォーマットのみ (アンラップ不可)

5.3 Sentinel Error vs Typed Error

// Sentinel — シンプルな比較
var ErrNotFound = errors.New("not found")

// Typed — 追加情報
type NotFoundError struct {
    Resource string
    ID       string
}
func (e *NotFoundError) Error() string { /* ... */ }

5.4 アンチパターン5つ

  1. if err != nilを消して無視
  2. ログ出力と伝播を同時に: 二重ログ
  3. panic()の乱用: 回復不能な時のみ
  4. wrapなしで伝播: スタック追跡不可
  5. エラー文字列が大文字・句読点付き: Go規約違反

第6部 — sync パッケージ: 並行プリミティブ

6.1 コア7つ

用途
sync.Mutex相互排他
sync.RWMutex多読み/一書き
sync.WaitGroupN個のGoroutine待ち
sync.Once一度だけ実行
sync.Cond条件変数
sync.Map並行マップ(特定パターンのみ有利)
sync.Poolオブジェクト再利用(GC圧軽減)

6.2 atomic パッケージ (Go 1.19+)

import "sync/atomic"

var counter atomic.Int64
counter.Add(1)
v := counter.Load()
counter.Store(100)

var ready atomic.Bool
ready.Store(true)

Mutexより速いが単純演算に限る。

6.3 Race Condition検出

go test -race ./...
go run -race main.go

本番投入前に必ず-race通過確認。 CI必須。


第7部 — Generics (Go 1.18+)

7.1 基本文法

func Map[T, U any](s []T, f func(T) U) []U {
    result := make([]U, len(s))
    for i, v := range s {
        result[i] = f(v)
    }
    return result
}

doubled := Map([]int{1, 2, 3}, func(n int) int { return n * 2 })

7.2 Type Constraint

import "golang.org/x/exp/constraints"

func Max[T constraints.Ordered](a, b T) T {
    if a > b { return a }
    return b
}

type Number interface {
    int | int64 | float64
}

func Sum[T Number](s []T) T {
    var total T
    for _, v := range s { total += v }
    return total
}

7.3 使うべきでない時

  • コンテナでない
  • interfaceで十分 (例: io.Reader)
  • コンパイル時間が重要

Goチーム公式アドバイス: 「迷ったら使うな。」


第8部 — 2024〜2025 Go Webフレームワーク比較

フレームワーク特徴適合
net/http標準ライブラリシンプル, 1.22+で強力
chiミニマル, 標準寄りマイクロサービス既定
gin成熟・人気スタートアップ, フルスタック
echoGin類似, 整然代替
fiberExpress風, fasthttp極限性能
humaOpenAPIファーストAPI契約重視

Go 1.22の強化net/httpルータ:

mux := http.NewServeMux()
mux.HandleFunc("GET /users/{id}", getUserHandler)
mux.HandleFunc("POST /users", createUserHandler)
http.ListenAndServe(":8080", mux)

1.22からメソッド・パス変数が標準。フレームワーク不要な場面が多い。

8.1 Chi例

import "github.com/go-chi/chi/v5"

r := chi.NewRouter()
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
r.Use(middleware.Timeout(5 * time.Second))

r.Route("/api/v1", func(r chi.Router) {
    r.Get("/users", listUsers)
    r.Post("/users", createUser)
    r.Get("/users/{id}", getUser)
})

http.ListenAndServe(":8080", r)

第9部 — gRPCとマイクロサービス実戦

9.1 なぜgRPCか

  • Protocol Buffers: 型安全 + 小ペイロード
  • HTTP/2: マルチプレクス, ヘッダ圧縮
  • Streaming: server, client, 双方向
  • Code Generation: 多言語整合

9.2 .proto ファイル

syntax = "proto3";
package user.v1;
option go_package = "example.com/gen/user/v1;userv1";

service UserService {
  rpc GetUser(GetUserRequest) returns (User);
  rpc ListUsers(ListUsersRequest) returns (stream User);
}

message User {
  string id = 1;
  string name = 2;
  int32 age = 3;
}

message GetUserRequest { string id = 1; }
message ListUsersRequest { int32 page_size = 1; }

9.3 buf + ConnectRPC (2025の標準)

  • buf: .protoのlint/build/破壊的変更検出
  • ConnectRPC: gRPC + REST + gRPC-Webを一括
import "connectrpc.com/connect"

type server struct{}

func (s *server) GetUser(
    ctx context.Context,
    req *connect.Request[userv1.GetUserRequest],
) (*connect.Response[userv1.User], error) {
    return connect.NewResponse(&userv1.User{Id: req.Msg.Id}), nil
}

9.4 マイクロサービスチェックリスト

  1. Observability 3軸: metrics (Prometheus), logs (structured), traces (OpenTelemetry)
  2. Context伝播: 全RPCにctxを渡す
  3. Retry with backoff
  4. Circuit Breaker: sony/gobreaker
  5. Timeout: 呼び出し毎に明示
  6. Health Check: grpc-health-probe
  7. Graceful Shutdown: SIGTERM時に既存要求を完了
  8. Rate Limit: golang.org/x/time/rate
  9. Request ID
  10. Deadline Propagation

第10部 — Go エコシステム 2024〜2025 の20選

カテゴリパッケージ
Webchi, gin, echo, fiber
gRPCgrpc-go, buf, connect-go
DBdatabase/sql + pgx, sqlc, ent, gorm
ログlog/slog (標準, 1.21+), zerolog
テストtestify, gomock, testcontainers-go
CLIcobra, viper, spf13/pflag
Observabilityprometheus/client_golang, opentelemetry-go
並行性golang.org/x/sync/errgroup, semaphore
HTTPクライアントnet/http + retryablehttp
JSONencoding/json + json-iterator/go

10.1 Go 1.23〜1.24の主要変更

  • Go 1.23 (2024/08): range over func の安定化, 改善されたtime.Timer
  • Go 1.24 (2025/02): Generic Type Alias, tool directive

第11部 — Rust vs Go 選択ガイド

11.1 決定木

Q1. メモリ・レイテンシが極限か?
  Yes -> Rust
  No  -> Q2

Q2. チーム規模が大きく、高速オンボード重視か?
  Yes -> Go
  No  -> Q3

Q3. システムプログラミング (kernel, DB, runtime)?
  Yes -> Rust
  No  -> Q4

Q4. Kubernetes/DevOps エコシステムか?
  Yes -> Go
  No  -> Q5

Q5. GC一時停止が許容不可か?
  Yes -> Rust
  No  -> Go (多くの場合)

11.2 冷静な比較

項目RustGo
学習曲線3〜6ヶ月2〜4週
コンパイル速度遅い非常に速い
実行時性能トップ級高い
メモリ使用非常に少ないGCオーバーヘッド
並行安全性コンパイル時実行時 (-race)
生態系成熟度急成長中非常に成熟
ドキュメント
型システム強力・複雑単純

日常判断:

  • バックエンドAPI -> Go (10分で作れる)
  • DB・ランタイム・プロキシ -> Rust
  • どちらでも -> チームの言語

第12部 — Go 6ヶ月ロードマップ

Month 1: 基礎

  • Tour of Go 完走
  • Effective Go 読了
  • CLIツール1本作成

Month 2: 並行性

  • Concurrency in Go (Katherine Cox-Buday)
  • Worker Poolを自作
  • -raceでrace練習

Month 3: 標準ライブラリを深く

  • net/http ソース読解
  • context パターン体得
  • errgroup + semaphore 実戦

Month 4: 実戦サーバ

  • chi + pgx + PostgreSQLでAPI
  • Observability 3軸 (slog + prometheus + otel)
  • testcontainersで統合テスト

Month 5: gRPC・マイクロサービス

  • Protocol Buffers
  • grpc-go もしくは connect-go
  • 3サービス間通信 + Observability

Month 6: OSS貢献

  • Kubernetes/Istio/Prometheusのissue 1つ解決

第13部 — Goチェックポイント12

  1. GoroutineとOSスレッドの違いを説明できる
  2. Channelの3状態 (nil, open, closed) の挙動を知る
  3. Context伝播5原則を言える
  4. errgroup の存在意義を知る
  5. Race Condition検出法を知る
  6. sync.Mutex vs sync.RWMutex の選択基準
  7. %w の意味を知る
  8. Goroutineリーク予防法
  9. net/http 1.22 ルータの新機能
  10. gRPC + buf + connect スタックを説明できる
  11. slog 構造化ロギングを使える
  12. Generic濫用の害を理解

第14部 — Goアンチパターン10

  1. Fire-and-forget Goroutine: キャンセル・完了待ちなし -> リーク
  2. Contextをstructフィールドに保持
  3. エラー返すべき所でpanic()
  4. interfaceにメソッド10個以上: 「小さいinterface」違反
  5. 空interface (any, interface{}) 濫用: 型安全性放棄
  6. goroutine内で共有変数を同期なしで読み書き: race
  7. fmt.Errorf("%v")でラップ: wrapにならない。%wを使う
  8. defer内でエラー無視: 必要なら無名関数で処理
  9. mapの同時アクセス: ランタイムpanic。sync.Mapsync.RWMutex
  10. nil interface比較: var e error = (*MyError)(nil); e != nil は true

おわりに — Goは「退屈だから強い」

Goは派手でない。文法は10分で学べる。最新トレンドもほぼない。

しかしその「退屈さ」こそチームの生産性だ。新人が2週間で本番コードを書き、10年前のコードがまだ読める。Kubernetesが500万行でも生き残る理由。

Rustが「間違えればコンパイルされない」なら、Goは**「多くの人が書いても皆が読める」**の哲学。

2025年、シニアエンジニアの道具箱にはRustとGoの両方を。状況に応じて出す。


次回予告 — TypeScript 完全ガイド

Season 2 Ep 4 は TypeScript。次回:

  • TypeScript 5.x の新型演算子
  • Generics と Conditional Type
  • Template Literal Type の実戦
  • Zod + tRPC + TanStackで型安全フルスタック
  • 2024〜2025 TS エコシステム (Turborepo, Bun, Deno)
  • AIと共にTypeScriptを書く

では次回。

현재 단락 (1/379)

Rustが「学習は難しいが強力」だとすれば、Goは「学びやすく、すぐ本番投入できる」側だ。

작성 글자: 0원문 글자: 10,604작성 단락: 0/379