Skip to content
Published on

Go プログラミング完全ガイド — ゴルーチン、チャネル、インターフェース、実践パターン

Authors

1. なぜGoなのか

Go(Golang)は2009年にGoogleで作られた言語です。設計哲学はシンプルさ、高速コンパイル、強力な並行処理です。

Goが選ばれる理由

  • Docker、Kubernetes、Terraform、Prometheus などクラウドインフラの中核ツールがすべてGoで書かれています
  • コンパイル速度が非常に速いです。数十万行のプロジェクトも数秒でビルドされます
  • 静的バイナリを生成するため、デプロイがシンプルです。別途のランタイムは不要です
  • ゴルーチンにより数十万の同時タスクを効率的に処理します
  • ガベージコレクションがありますが、レイテンシは非常に低いです

Go vs 他の言語

項目GoPythonJavaRust
コンパイル速度非常に速いインタプリタ遅い遅い
実行速度速い遅い速い非常に速い
並行処理ゴルーチンasyncioスレッドasync/tokio
学習コスト低い非常に低い高い非常に高い
メモリ管理GCGCGC所有権

Goは生産性とパフォーマンスの最適なバランスポイントに位置します。Rustほど速くはありませんが、チーム全体が素早く学び、メンテナンスできます。


2. 基本文法

変数と型

package main

import "fmt"

func main() {
    // 明示的な宣言
    var name string = "Gopher"
    var age int = 10

    // 短い宣言(型推論)
    language := "Go"
    version := 1.22

    // 定数
    const pi = 3.14159

    fmt.Printf("%sは%d歳で%s %.2fを使っています\n",
        name, age, language, version)
    fmt.Println("円周率:", pi)
}

関数

// 基本関数
func add(a, b int) int {
    return a + b
}

// 複数戻り値
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("ゼロで割ることはできません")
    }
    return a / b, nil
}

// 名前付き戻り値
func swap(a, b string) (first, second string) {
    first = b
    second = a
    return // naked return
}

// 可変長引数
func sum(nums ...int) int {
    total := 0
    for _, n := range nums {
        total += n
    }
    return total
}

構造体

type User struct {
    Name  string
    Email string
    Age   int
}

// メソッド(値レシーバ)
func (u User) String() string {
    return fmt.Sprintf("%s (%s)", u.Name, u.Email)
}

// メソッド(ポインタレシーバ - 値の変更可能)
func (u *User) SetEmail(email string) {
    u.Email = email
}

func main() {
    user := User{Name: "田中太郎", Email: "tanaka@example.com", Age: 30}
    user.SetEmail("new@example.com")
    fmt.Println(user) // 田中太郎 (new@example.com)
}

ポインタ

func main() {
    x := 42
    p := &x    // xのアドレス
    fmt.Println(*p) // 42(デリファレンス)
    *p = 100
    fmt.Println(x) // 100
}

// Goにはポインタ演算がありません - 安全です
// Cと違い、ダングリングポインタの心配が少ないです

スライスとマップ

func main() {
    // スライス
    nums := []int{1, 2, 3, 4, 5}
    nums = append(nums, 6, 7)
    sub := nums[1:4] // [2, 3, 4]

    // makeで作成
    buffer := make([]byte, 0, 1024) // len=0, cap=1024

    // マップ
    scores := map[string]int{
        "Alice": 95,
        "Bob":   87,
    }
    scores["Charlie"] = 92

    // キーの存在確認
    val, ok := scores["Dave"]
    if !ok {
        fmt.Println("Daveのスコアがありません")
    }

    // マップの反復
    for name, score := range scores {
        fmt.Printf("%s: %d\n", name, score)
    }
}

3. ゴルーチンとチャネル

Goの並行処理モデルはCSP(Communicating Sequential Processes) に基づいています。核心は2つ:ゴルーチンチャネルです。

ゴルーチンの基礎

func worker(id int) {
    fmt.Printf("Worker %d 開始\n", id)
    time.Sleep(time.Second)
    fmt.Printf("Worker %d 完了\n", id)
}

func main() {
    for i := 1; i <= 5; i++ {
        go worker(i) // ゴルーチン起動
    }

    // メインゴルーチンが終了するとプログラムも終了
    time.Sleep(2 * time.Second)
}

ゴルーチンはOSスレッドではありません。Goランタイムが数千のゴルーチンを少数のOSスレッドに多重化(multiplexing) します。ゴルーチン1つの初期スタックは約2KBで、数十万個を同時に実行できます。

チャネル通信

func producer(ch chan<- int) {
    for i := 0; i < 5; i++ {
        ch <- i // チャネルに値を送信
        fmt.Printf("送信: %d\n", i)
    }
    close(ch) // チャネルを閉じる
}

func consumer(ch <-chan int) {
    for val := range ch { // チャネルが閉じるまで受信
        fmt.Printf("受信: %d\n", val)
    }
}

func main() {
    ch := make(chan int, 3) // バッファサイズ3

    go producer(ch)
    consumer(ch) // メインゴルーチンで消費
}

select文

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)

    go func() {
        time.Sleep(1 * time.Second)
        ch1 <- "one"
    }()

    go func() {
        time.Sleep(2 * time.Second)
        ch2 <- "two"
    }()

    for i := 0; i < 2; i++ {
        select {
        case msg := <-ch1:
            fmt.Println("ch1:", msg)
        case msg := <-ch2:
            fmt.Println("ch2:", msg)
        case <-time.After(3 * time.Second):
            fmt.Println("タイムアウト!")
        }
    }
}

WaitGroup

func main() {
    var wg sync.WaitGroup

    urls := []string{
        "https://example.com",
        "https://golang.org",
        "https://github.com",
    }

    for _, url := range urls {
        wg.Add(1)
        go func(u string) {
            defer wg.Done()
            resp, err := http.Get(u)
            if err != nil {
                fmt.Printf("エラー: %s - %v\n", u, err)
                return
            }
            defer resp.Body.Close()
            fmt.Printf("%s -> %s\n", u, resp.Status)
        }(url)
    }

    wg.Wait() // 全ゴルーチンの完了を待つ
    fmt.Println("全リクエスト完了")
}

デッドロック防止パターン

// 悪い例:デッドロック発生
func bad() {
    ch := make(chan int) // バッファなしチャネル
    ch <- 1             // 受信者がいないため永久にブロック
    fmt.Println(<-ch)
}

// 良い例1:ゴルーチンで送信
func good1() {
    ch := make(chan int)
    go func() { ch <- 1 }()
    fmt.Println(<-ch)
}

// 良い例2:バッファチャネルを使用
func good2() {
    ch := make(chan int, 1)
    ch <- 1
    fmt.Println(<-ch)
}

ワーカープールパターン

func workerPool(jobs <-chan int, results chan<- int, id int) {
    for j := range jobs {
        fmt.Printf("Worker %d 処理中: job %d\n", id, j)
        time.Sleep(time.Millisecond * 500)
        results <- j * 2
    }
}

func main() {
    const numJobs = 10
    const numWorkers = 3

    jobs := make(chan int, numJobs)
    results := make(chan int, numJobs)

    // ワーカー起動
    for w := 1; w <= numWorkers; w++ {
        go workerPool(jobs, results, w)
    }

    // ジョブ送信
    for j := 1; j <= numJobs; j++ {
        jobs <- j
    }
    close(jobs)

    // 結果収集
    for r := 1; r <= numJobs; r++ {
        result := <-results
        fmt.Printf("結果: %d\n", result)
    }
}

4. インターフェース

Goのインターフェースは暗黙的に実装されます。implementsキーワードは不要です。

基本インターフェース

type Shape interface {
    Area() float64
    Perimeter() float64
}

type Circle struct {
    Radius float64
}

func (c Circle) Area() float64 {
    return math.Pi * c.Radius * c.Radius
}

func (c Circle) Perimeter() float64 {
    return 2 * math.Pi * c.Radius
}

type Rectangle struct {
    Width, Height float64
}

func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

func (r Rectangle) Perimeter() float64 {
    return 2 * (r.Width + r.Height)
}

// Shapeインターフェースをパラメータとして受け取る関数
func printInfo(s Shape) {
    fmt.Printf("面積: %.2f, 周囲: %.2f\n", s.Area(), s.Perimeter())
}

io.Reader / io.Writer

Go標準ライブラリで最も強力なインターフェースです。

// io.Readerインターフェース
// type Reader interface {
//     Read(p []byte) (n int, err error)
// }

// io.Writerインターフェース
// type Writer interface {
//     Write(p []byte) (n int, err error)
// }

func countBytes(r io.Reader) (int, error) {
    buf := make([]byte, 1024)
    total := 0
    for {
        n, err := r.Read(buf)
        total += n
        if err == io.EOF {
            return total, nil
        }
        if err != nil {
            return total, err
        }
    }
}

func main() {
    // 文字列から読み取り
    r := strings.NewReader("Hello, Go!")
    n, _ := countBytes(r)
    fmt.Printf("%d バイト\n", n) // 10 バイト

    // ファイルから読み取り
    f, _ := os.Open("data.txt")
    defer f.Close()
    n, _ = countBytes(f)
    fmt.Printf("%d バイト\n", n)
}

空インターフェースと型アサーション

func describe(i interface{}) {
    // 型アサーション
    if s, ok := i.(string); ok {
        fmt.Println("文字列:", s)
        return
    }

    // 型スイッチ
    switch v := i.(type) {
    case int:
        fmt.Println("整数:", v)
    case float64:
        fmt.Println("実数:", v)
    case bool:
        fmt.Println("ブール:", v)
    default:
        fmt.Printf("不明な型: %T\n", v)
    }
}

func main() {
    describe(42)
    describe("hello")
    describe(3.14)
    describe(true)
}

インターフェースの合成

type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

type Closer interface {
    Close() error
}

// インターフェース合成(埋め込み)
type ReadWriter interface {
    Reader
    Writer
}

type ReadWriteCloser interface {
    Reader
    Writer
    Closer
}

5. エラー処理

Goは例外(exception)の代わりに明示的なエラー戻り値を使います。

errors.Newとfmt.Errorf

import (
    "errors"
    "fmt"
)

var ErrNotFound = errors.New("not found")

func findUser(id int) (*User, error) {
    if id <= 0 {
        return nil, fmt.Errorf("無効なユーザーID: %d", id)
    }
    // ... DB検索
    return nil, ErrNotFound
}

エラーラッピングとerrors.Is / errors.As

type ValidationError struct {
    Field   string
    Message string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("バリデーション失敗 - %s: %s", e.Field, e.Message)
}

func validateAge(age int) error {
    if age < 0 || age > 150 {
        return &ValidationError{
            Field:   "age",
            Message: fmt.Sprintf("年齢は0-150の範囲でなければなりません(入力値: %d)", age),
        }
    }
    return nil
}

func processUser(age int) error {
    if err := validateAge(age); err != nil {
        return fmt.Errorf("ユーザー処理失敗: %w", err) // エラーラッピング
    }
    return nil
}

func main() {
    err := processUser(-5)

    // errors.Is: エラーチェーンで特定のエラーを確認
    if errors.Is(err, ErrNotFound) {
        fmt.Println("ユーザーが見つかりません")
    }

    // errors.As: エラーチェーンで特定の型に変換
    var valErr *ValidationError
    if errors.As(err, &valErr) {
        fmt.Printf("フィールド: %s, メッセージ: %s\n", valErr.Field, valErr.Message)
    }
}

カスタムエラーパターン

type AppError struct {
    Code    int
    Message string
    Err     error
}

func (e *AppError) Error() string {
    if e.Err != nil {
        return fmt.Sprintf("[%d] %s: %v", e.Code, e.Message, e.Err)
    }
    return fmt.Sprintf("[%d] %s", e.Code, e.Message)
}

func (e *AppError) Unwrap() error {
    return e.Err
}

// エラー生成ヘルパー
func NewAppError(code int, msg string, err error) *AppError {
    return &AppError{Code: code, Message: msg, Err: err}
}

6. ジェネリクス(Go 1.18以降)

Go 1.18からジェネリクスが導入され、型安全な汎用コードが書けるようになりました。

型パラメータ

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

func main() {
    fmt.Println(Min(3, 5))       // int
    fmt.Println(Min(3.14, 2.71)) // float64
    fmt.Println(Min("a", "b"))   // string
}

制約

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

// カスタム制約
type Number interface {
    constraints.Integer | constraints.Float
}

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

// 構造体にジェネリクスを適用
type Stack[T any] struct {
    items []T
}

func (s *Stack[T]) Push(item T) {
    s.items = append(s.items, item)
}

func (s *Stack[T]) Pop() (T, bool) {
    if len(s.items) == 0 {
        var zero T
        return zero, false
    }
    item := s.items[len(s.items)-1]
    s.items = s.items[:len(s.items)-1]
    return item, true
}

func (s *Stack[T]) Len() int {
    return len(s.items)
}

実践ジェネリクス例:マップユーティリティ

func Keys[K comparable, V any](m map[K]V) []K {
    keys := make([]K, 0, len(m))
    for k := range m {
        keys = append(keys, k)
    }
    return keys
}

func Values[K comparable, V any](m map[K]V) []V {
    vals := make([]V, 0, len(m))
    for _, v := range m {
        vals = append(vals, v)
    }
    return vals
}

func Filter[T any](slice []T, predicate func(T) bool) []T {
    var result []T
    for _, item := range slice {
        if predicate(item) {
            result = append(result, item)
        }
    }
    return result
}

func Map[T any, U any](slice []T, transform func(T) U) []U {
    result := make([]U, len(slice))
    for i, item := range slice {
        result[i] = transform(item)
    }
    return result
}

7. テスト

Goには強力なビルトインテストフレームワークがあります。外部ライブラリは不要です。

基本テスト

// math.go
package math

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

func Fibonacci(n int) int {
    if n <= 1 {
        return n
    }
    return Fibonacci(n-1) + Fibonacci(n-2)
}
// math_test.go
package math

import "testing"

func TestAdd(t *testing.T) {
    result := Add(2, 3)
    if result != 5 {
        t.Errorf("Add(2, 3) = %d; want 5", result)
    }
}

テーブル駆動テスト

func TestFibonacci(t *testing.T) {
    tests := []struct {
        name     string
        input    int
        expected int
    }{
        {"zero", 0, 0},
        {"one", 1, 1},
        {"two", 2, 1},
        {"five", 5, 5},
        {"ten", 10, 55},
    }

    for _, tc := range tests {
        t.Run(tc.name, func(t *testing.T) {
            result := Fibonacci(tc.input)
            if result != tc.expected {
                t.Errorf("Fibonacci(%d) = %d; want %d",
                    tc.input, result, tc.expected)
            }
        })
    }
}

ベンチマーク

func BenchmarkFibonacci(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Fibonacci(20)
    }
}

// 実行: go test -bench=. -benchmem
// BenchmarkFibonacci-8  28735  41523 ns/op  0 B/op  0 allocs/op

httptest

func TestHealthHandler(t *testing.T) {
    req := httptest.NewRequest("GET", "/health", nil)
    w := httptest.NewRecorder()

    healthHandler(w, req)

    resp := w.Result()
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        t.Errorf("ステータスコード = %d; want %d",
            resp.StatusCode, http.StatusOK)
    }

    body, _ := io.ReadAll(resp.Body)
    if string(body) != `{"status":"ok"}` {
        t.Errorf("レスポンス = %s; want {\"status\":\"ok\"}", body)
    }
}

8. パッケージとモジュール

go modの初期化

# 新規プロジェクト開始
mkdir myproject && cd myproject
go mod init github.com/username/myproject

# 依存関係を追加
go get github.com/gin-gonic/gin@latest

# 未使用の依存関係をクリーンアップ
go mod tidy

# 依存関係をダウンロード
go mod download

プロジェクト構成

myproject/
  cmd/
    server/
      main.go          # エントリポイント
  internal/
    handler/
      user.go          # HTTPハンドラ
      user_test.go
    service/
      user.go          # ビジネスロジック
    repository/
      user.go          # データアクセス
  pkg/
    validator/
      validator.go     # 外部公開ユーティリティ
  go.mod
  go.sum

internalパッケージ

internalディレクトリ内のパッケージは、そのモジュール外部からインポートできません。これにより公開APIと内部実装を明確に分離します。

// internal/config/config.go
package config

type Config struct {
    Port     int
    DBHost   string
    LogLevel string
}

func Load() (*Config, error) {
    // 環境変数から設定をロード
    return &Config{
        Port:     8080,
        DBHost:   "localhost:5432",
        LogLevel: "info",
    }, nil
}

9. 実践パターン

Webサーバー(net/http)

package main

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

type Response struct {
    Message   string `json:"message"`
    Timestamp string `json:"timestamp"`
}

func healthHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(Response{
        Message:   "ok",
        Timestamp: time.Now().Format(time.RFC3339),
    })
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("GET /health", healthHandler)
    mux.HandleFunc("GET /api/users", getUsersHandler)

    server := &http.Server{
        Addr:         ":8080",
        Handler:      mux,
        ReadTimeout:  10 * time.Second,
        WriteTimeout: 10 * time.Second,
        IdleTimeout:  60 * time.Second,
    }

    log.Printf("サーバー起動: %s", server.Addr)
    log.Fatal(server.ListenAndServe())
}

ミドルウェア

func loggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        log.Printf("[%s] %s 開始", r.Method, r.URL.Path)

        next.ServeHTTP(w, r)

        log.Printf("[%s] %s 完了 (%v)",
            r.Method, r.URL.Path, time.Since(start))
    })
}

func authMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        token := r.Header.Get("Authorization")
        if token == "" {
            http.Error(w, "認証が必要です", http.StatusUnauthorized)
            return
        }
        // トークン検証ロジック...
        next.ServeHTTP(w, r)
    })
}

// ミドルウェアチェーン
func chain(h http.Handler, middlewares ...func(http.Handler) http.Handler) http.Handler {
    for i := len(middlewares) - 1; i >= 0; i-- {
        h = middlewares[i](h)
    }
    return h
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("GET /health", healthHandler)

    handler := chain(mux, loggingMiddleware, authMiddleware)

    log.Fatal(http.ListenAndServe(":8080", handler))
}

Graceful Shutdown

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("GET /health", healthHandler)

    server := &http.Server{
        Addr:    ":8080",
        Handler: mux,
    }

    // ゴルーチンでサーバーを起動
    go func() {
        log.Println("サーバー起動: :8080")
        if err := server.ListenAndServe(); err != http.ErrServerClosed {
            log.Fatalf("サーバーエラー: %v", err)
        }
    }()

    // 終了シグナルを待つ
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    <-quit
    log.Println("サーバー終了開始...")

    // 30秒タイムアウトでgraceful shutdown
    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()

    if err := server.Shutdown(ctx); err != nil {
        log.Fatalf("サーバー強制終了: %v", err)
    }

    log.Println("サーバー終了完了")
}

設定管理

type Config struct {
    Server   ServerConfig
    Database DatabaseConfig
}

type ServerConfig struct {
    Port         int           `json:"port"`
    ReadTimeout  time.Duration `json:"read_timeout"`
    WriteTimeout time.Duration `json:"write_timeout"`
}

type DatabaseConfig struct {
    Host     string `json:"host"`
    Port     int    `json:"port"`
    User     string `json:"user"`
    Password string `json:"password"`
    DBName   string `json:"dbname"`
}

func (d DatabaseConfig) DSN() string {
    return fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable",
        d.Host, d.Port, d.User, d.Password, d.DBName)
}

func LoadConfig(path string) (*Config, error) {
    data, err := os.ReadFile(path)
    if err != nil {
        return nil, fmt.Errorf("設定ファイルの読み込み失敗: %w", err)
    }

    var cfg Config
    if err := json.Unmarshal(data, &cfg); err != nil {
        return nil, fmt.Errorf("設定のパース失敗: %w", err)
    }

    return &cfg, nil
}

10. 2026年のGo

クラウドインフラ

Goはクラウドネイティブエコシステムの事実上の標準言語です。

  • コンテナオーケストレーション: Kubernetes、Docker、containerd
  • サービスメッシュ: Istio、Linkerd
  • 監視: Prometheus、Grafana Agent、Thanos
  • IaC: Terraform、Pulumi
  • CI/CD: Drone、Tekton

CLIツール

Goで作ったCLIツールは単一バイナリで配布され、インストールが簡単です。

// cobraを使ったCLI例
package main

import (
    "fmt"
    "os"

    "github.com/spf13/cobra"
)

var rootCmd = &cobra.Command{
    Use:   "mytool",
    Short: "私のCLIツール",
}

var greetCmd = &cobra.Command{
    Use:   "greet [name]",
    Short: "挨拶する",
    Args:  cobra.ExactArgs(1),
    Run: func(cmd *cobra.Command, args []string) {
        fmt.Printf("こんにちは、%sさん!\n", args[0])
    },
}

func main() {
    rootCmd.AddCommand(greetCmd)
    if err := rootCmd.Execute(); err != nil {
        os.Exit(1)
    }
}

マイクロサービス

Goはマイクロサービスアーキテクチャに最適化されています。

  • 高速な起動時間: コールドスタート遅延を最小化
  • 低メモリ使用量: コンテナリソースを節約
  • 静的バイナリ: 軽量Dockerイメージ(scratchベース可能)
  • gRPCネイティブサポート: protobuf + gRPCでサービス間通信
  • OpenTelemetry: 分散トレーシングとメトリクス収集
# マルチステージビルド
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /server ./cmd/server

FROM scratch
COPY --from=builder /server /server
EXPOSE 8080
ENTRYPOINT ["/server"]

最終イメージサイズは10-20MB程度で、JavaやPythonベースのサービスと比べて1/10以下です。


おわりに

Goはシンプルさこそが強さという言語です。複雑な機能を排除し、本当に必要なものだけを提供します。この哲学が、DockerやKubernetesのようなインフラツールからCLI、マイクロサービスまで幅広い領域でGoが選ばれる理由です。

始めるのに最も良い方法は、自分でコードを書くことです。シンプルなCLIツールやREST APIサーバーを作ってみてください。

クイズ:Go並行処理の理解度チェック

Q1. ゴルーチンとOSスレッドの違いは何ですか?

Goランタイムがゴルーチンを少数のOSスレッドに多重化します。ゴルーチンの初期スタックは約2KBで、OSスレッドの1-8MBよりはるかに軽量です。

Q2. バッファなしチャネルでの送信と受信の関係は?

送信者は受信者の準備ができるまでブロックし、受信者も送信者の準備ができるまでブロックします。同期的な通信方式です。

Q3. select文の役割は何ですか?

複数のチャネル操作を同時に待ちながら、準備ができたチャネルから先に処理します。タイムアウトやキャンセル処理に有用です。