9
0

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(Golang)は、Googleが開発したシンプルで高速なプログラミング言語です。シンプルな文法、高速なコンパイル、強力な並行処理機能が特徴で、Docker、Kubernetes、Terraformなど多くの有名なツールがGoで書かれています。

この記事では、Goの基礎から実践的な並行処理まで、詳しく解説します。

環境構築

# macOS (Homebrew)
brew install go

# バージョン確認
go version

# 環境変数の確認
go env

# プロジェクトの初期化
mkdir my-project && cd my-project
go mod init github.com/username/my-project

ただ、実務で詰まりやすいのは文法ではなく

  • goroutine を増やしすぎて止まらない
  • context を渡さずにタイムアウトが効かない
  • エラーの握りつぶしで障害解析ができない
  • 並行処理の終了条件が曖昧でリークする

といった設計と運用の部分です。
この記事は構文の紹介に加えて
何を方針として固定すべきかの判断軸も先に置きます。

まず押さえる設計判断の軸

並行処理は 目的 から逆算する

  • I/O待ちを隠したい(HTTP、DB、外部API)
  • バックグラウンドで処理したい(ワーカー)
  • スループットを上げたい(並列)

目的が違うと、必要な制御も違います。
goroutine は軽いですが、無限に安全ではありません。

goroutine と channel は終了条件がセット

  • goroutine を起動したら、停止の合図を設計する
  • channel を作ったら、closeする責任者を決める

ここが曖昧だとリークします。

context は境界で必ず受け取り 必ず渡す

HTTPハンドラ、RPC、ジョブ実行など「境界」から中に入るときに context.Context を最初に受け取り、下層へ渡す習慣を付けると障害対応が楽になります。

エラーは情報を足して返す

  • 呼び出し元が判断できる情報(原因、種類)
  • ログに出すべき情報(リクエストID、引数の一部)

を足して返すと、調査が速くなります。

最小チェックリスト(レビュー用)

  • goroutine の起動に停止条件がある
  • channel の close の責務が明確
  • context が境界から渡っている
  • エラーが握りつぶされていない
  • 共有変数を複数goroutineから触っていない(必要なら mutex か channel)

基本文法

Hello World

package main

import "fmt"

func main() {
    fmt.Println("Hello, World!")
}
# 実行
go run main.go

# ビルド
go build -o myapp main.go
./myapp

変数と定数

package main

import "fmt"

func main() {
    // 変数宣言
    var name string = "Go"
    var age int = 15
    var isAwesome bool = true

    // 型推論
    var city = "Tokyo"
    
    // 短縮宣言(関数内のみ)
    country := "Japan"
    
    // 複数変数の宣言
    var (
        width  = 100
        height = 200
    )
    
    x, y, z := 1, 2, 3

    // 定数
    const Pi = 3.14159
    const (
        StatusOK    = 200
        StatusError = 500
    )
    
    // iota(連番定数)
    const (
        Sunday = iota  // 0
        Monday         // 1
        Tuesday        // 2
    )

    fmt.Println(name, age, isAwesome, city, country)
    fmt.Println(width, height, x, y, z)
    fmt.Println(Pi, StatusOK)
}

基本型

// 整数
var i int = 42        // プラットフォーム依存(32 or 64bit)
var i8 int8 = 127     // -128 〜 127
var i16 int16         // -32768 〜 32767
var i32 int32         // -2^31 〜 2^31-1
var i64 int64         // -2^63 〜 2^63-1

// 符号なし整数
var u uint = 42
var u8 uint8 = 255    // 0 〜 255 (byte)
var u16 uint16
var u32 uint32
var u64 uint64

// 浮動小数点
var f32 float32 = 3.14
var f64 float64 = 3.14159265359

// 複素数
var c64 complex64 = 1 + 2i
var c128 complex128

// 文字列
var s string = "Hello"

// 文字(rune = int32)
var r rune = 'あ'

// バイト(uint8のエイリアス)
var b byte = 65  // 'A'

// 真偽値
var flag bool = true

ゼロ値

var i int       // 0
var f float64   // 0.0
var s string    // ""
var b bool      // false
var p *int      // nil
var sl []int    // nil
var m map[string]int  // nil

文字列操作

package main

import (
    "fmt"
    "strings"
)

func main() {
    s := "Hello, World!"
    
    // 長さ
    fmt.Println(len(s))  // 13(バイト数)
    
    // 文字列の結合
    greeting := "Hello" + " " + "Go"
    fmt.Println(greeting)
    
    // strings パッケージ
    fmt.Println(strings.ToUpper(s))           // "HELLO, WORLD!"
    fmt.Println(strings.ToLower(s))           // "hello, world!"
    fmt.Println(strings.Contains(s, "World")) // true
    fmt.Println(strings.HasPrefix(s, "Hello")) // true
    fmt.Println(strings.HasSuffix(s, "!"))    // true
    fmt.Println(strings.Index(s, "World"))    // 7
    fmt.Println(strings.Replace(s, "World", "Go", 1))
    fmt.Println(strings.Split(s, ", "))       // ["Hello", "World!"]
    fmt.Println(strings.TrimSpace("  hello  ")) // "hello"
    
    // 文字列のフォーマット
    name := "Alice"
    age := 25
    message := fmt.Sprintf("Name: %s, Age: %d", name, age)
    fmt.Println(message)
    
    // 生文字列(バッククォート)
    raw := `Line 1
Line 2
Line 3`
    fmt.Println(raw)
}

配列とスライス

package main

import "fmt"

func main() {
    // 配列(固定長)
    var arr [5]int = [5]int{1, 2, 3, 4, 5}
    arr2 := [...]int{1, 2, 3}  // 要素数を自動で決定
    
    fmt.Println(arr[0])   // 1
    fmt.Println(len(arr)) // 5
    
    // スライス(可変長)
    slice := []int{1, 2, 3, 4, 5}
    
    // make でスライス作成
    slice2 := make([]int, 5)      // 長さ5、容量5
    slice3 := make([]int, 0, 10)  // 長さ0、容量10
    
    fmt.Println(len(slice3), cap(slice3))  // 0 10
    
    // スライシング
    fmt.Println(slice[1:3])  // [2 3]
    fmt.Println(slice[:3])   // [1 2 3]
    fmt.Println(slice[2:])   // [3 4 5]
    
    // 要素の追加
    slice = append(slice, 6, 7)
    fmt.Println(slice)  // [1 2 3 4 5 6 7]
    
    // スライスの結合
    slice4 := []int{8, 9}
    slice = append(slice, slice4...)
    
    // コピー
    dst := make([]int, len(slice))
    copy(dst, slice)
    
    // 要素の削除(i番目)
    i := 2
    slice = append(slice[:i], slice[i+1:]...)
}

マップ

package main

import "fmt"

func main() {
    // マップの宣言
    var m map[string]int
    m = make(map[string]int)
    
    // リテラルで初期化
    scores := map[string]int{
        "Alice": 90,
        "Bob":   85,
        "Carol": 92,
    }
    
    // 要素へのアクセス
    fmt.Println(scores["Alice"])  // 90
    
    // 要素の追加・更新
    scores["Dave"] = 88
    scores["Alice"] = 95
    
    // 要素の削除
    delete(scores, "Bob")
    
    // 存在確認
    value, exists := scores["Eve"]
    if exists {
        fmt.Println(value)
    } else {
        fmt.Println("Not found")
    }
    
    // 長さ
    fmt.Println(len(scores))
    
    // イテレーション
    for key, value := range scores {
        fmt.Printf("%s: %d\n", key, value)
    }
}

制御構文

package main

import "fmt"

func main() {
    // if文
    x := 10
    if x > 5 {
        fmt.Println("x > 5")
    } else if x > 0 {
        fmt.Println("0 < x <= 5")
    } else {
        fmt.Println("x <= 0")
    }
    
    // 初期化文付きif
    if y := getValue(); y > 0 {
        fmt.Println(y)
    }
    
    // for文(Goにはwhileがない)
    for i := 0; i < 5; i++ {
        fmt.Println(i)
    }
    
    // while相当
    count := 0
    for count < 5 {
        fmt.Println(count)
        count++
    }
    
    // 無限ループ
    // for {
    //     // break で抜ける
    // }
    
    // range
    slice := []string{"a", "b", "c"}
    for index, value := range slice {
        fmt.Printf("%d: %s\n", index, value)
    }
    
    // インデックスのみ
    for i := range slice {
        fmt.Println(i)
    }
    
    // 値のみ(インデックス無視)
    for _, value := range slice {
        fmt.Println(value)
    }
    
    // switch文
    day := "Monday"
    switch day {
    case "Monday":
        fmt.Println("Start of the week")
    case "Friday":
        fmt.Println("End of the work week")
    case "Saturday", "Sunday":
        fmt.Println("Weekend")
    default:
        fmt.Println("Midweek")
    }
    
    // 条件式なしのswitch
    score := 85
    switch {
    case score >= 90:
        fmt.Println("A")
    case score >= 80:
        fmt.Println("B")
    case score >= 70:
        fmt.Println("C")
    default:
        fmt.Println("F")
    }
}

func getValue() int {
    return 42
}

関数

package main

import "fmt"

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

// 同じ型の引数は省略可能
func multiply(a, b int) int {
    return a * b
}

// 複数の戻り値
func divide(a, b int) (int, int) {
    return a / b, a % b
}

// 名前付き戻り値
func divideNamed(a, b int) (quotient, remainder int) {
    quotient = a / b
    remainder = a % b
    return  // naked return
}

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

// 関数を引数に取る
func apply(fn func(int, int) int, a, b int) int {
    return fn(a, b)
}

// 関数を返す(クロージャ)
func counter() func() int {
    count := 0
    return func() int {
        count++
        return count
    }
}

func main() {
    fmt.Println(add(1, 2))        // 3
    fmt.Println(multiply(3, 4))   // 12
    
    q, r := divide(10, 3)
    fmt.Println(q, r)             // 3 1
    
    fmt.Println(sum(1, 2, 3, 4))  // 10
    
    // スライスを展開して渡す
    nums := []int{1, 2, 3, 4, 5}
    fmt.Println(sum(nums...))     // 15
    
    // 無名関数
    double := func(n int) int {
        return n * 2
    }
    fmt.Println(double(5))        // 10
    
    // 関数を引数に渡す
    result := apply(multiply, 3, 4)
    fmt.Println(result)           // 12
    
    // クロージャ
    c := counter()
    fmt.Println(c())  // 1
    fmt.Println(c())  // 2
    fmt.Println(c())  // 3
}

ポインタ

package main

import "fmt"

func main() {
    x := 10
    
    // ポインタの取得
    p := &x
    
    fmt.Println(p)   // 0xc0000a0010(アドレス)
    fmt.Println(*p)  // 10(デリファレンス)
    
    // ポインタを通じて値を変更
    *p = 20
    fmt.Println(x)   // 20
    
    // 新しいポインタを作成
    var q *int = new(int)
    *q = 30
    fmt.Println(*q)  // 30
}

// 値渡し(コピー)
func incrementValue(n int) {
    n++
}

// ポインタ渡し(参照)
func incrementPointer(n *int) {
    *n++
}

func demo() {
    x := 10
    incrementValue(x)
    fmt.Println(x)  // 10(変わらない)
    
    incrementPointer(&x)
    fmt.Println(x)  // 11(変わる)
}

構造体

package main

import "fmt"

// 構造体の定義
type Person struct {
    Name    string
    Age     int
    Email   string
}

// 埋め込み(継承の代わり)
type Employee struct {
    Person        // 埋め込み
    EmployeeID string
    Department string
}

// メソッド
func (p Person) Greet() string {
    return fmt.Sprintf("Hello, I'm %s", p.Name)
}

// ポインタレシーバ(値を変更可能)
func (p *Person) SetAge(age int) {
    p.Age = age
}

// 値レシーバ(値は変更されない)
func (p Person) GetAge() int {
    return p.Age
}

func main() {
    // 構造体の作成
    p1 := Person{
        Name:  "Alice",
        Age:   25,
        Email: "alice@example.com",
    }
    
    // フィールドの順序で初期化
    p2 := Person{"Bob", 30, "bob@example.com"}
    
    // ゼロ値で初期化
    var p3 Person
    p3.Name = "Carol"
    
    // ポインタで作成
    p4 := &Person{
        Name: "Dave",
        Age:  35,
    }
    
    // newを使用
    p5 := new(Person)
    p5.Name = "Eve"
    
    fmt.Println(p1, p2, p3, p4, p5)
    
    // メソッドの呼び出し
    fmt.Println(p1.Greet())
    
    p1.SetAge(26)
    fmt.Println(p1.Age)  // 26
    
    // 埋め込み
    emp := Employee{
        Person: Person{
            Name: "Frank",
            Age:  28,
        },
        EmployeeID: "E001",
        Department: "Engineering",
    }
    
    // 埋め込まれたフィールドに直接アクセス
    fmt.Println(emp.Name)  // Frank
    fmt.Println(emp.Greet())  // 埋め込まれたメソッドも使える
}

インターフェース

package main

import (
    "fmt"
    "math"
)

// インターフェースの定義
type Shape interface {
    Area() float64
    Perimeter() float64
}

// Circle 型
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
}

// Rectangle 型
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)
}

// インターフェースを引数に取る関数
func printShapeInfo(s Shape) {
    fmt.Printf("Area: %.2f, Perimeter: %.2f\n", s.Area(), s.Perimeter())
}

// 空インターフェース(any型)
func printAny(v interface{}) {
    fmt.Printf("Type: %T, Value: %v\n", v, v)
}

// 型アサーション
func describe(i interface{}) {
    switch v := i.(type) {
    case int:
        fmt.Printf("Integer: %d\n", v)
    case string:
        fmt.Printf("String: %s\n", v)
    case Circle:
        fmt.Printf("Circle with radius: %.2f\n", v.Radius)
    default:
        fmt.Printf("Unknown type: %T\n", v)
    }
}

func main() {
    c := Circle{Radius: 5}
    r := Rectangle{Width: 3, Height: 4}
    
    printShapeInfo(c)
    printShapeInfo(r)
    
    // スライスにまとめる
    shapes := []Shape{c, r}
    for _, s := range shapes {
        printShapeInfo(s)
    }
    
    // 型アサーション
    var s Shape = Circle{Radius: 3}
    
    // 型を確認して変換
    if circle, ok := s.(Circle); ok {
        fmt.Println("Radius:", circle.Radius)
    }
    
    // 空インターフェース
    printAny(42)
    printAny("hello")
    printAny(c)
    
    // 型スイッチ
    describe(42)
    describe("hello")
    describe(c)
}

エラーハンドリング

package main

import (
    "errors"
    "fmt"
)

// エラーを返す関数
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("division by zero")
    }
    return a / b, nil
}

// カスタムエラー
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 {
        return &ValidationError{
            Field:   "age",
            Message: "must be non-negative",
        }
    }
    if age > 150 {
        return &ValidationError{
            Field:   "age",
            Message: "unrealistic value",
        }
    }
    return nil
}

// エラーのラップ(Go 1.13+)
func processFile(filename string) error {
    _, err := openFile(filename)
    if err != nil {
        return fmt.Errorf("failed to process %s: %w", filename, err)
    }
    return nil
}

func openFile(filename string) (string, error) {
    return "", errors.New("file not found")
}

func main() {
    // 基本的なエラーハンドリング
    result, err := divide(10, 0)
    if err != nil {
        fmt.Println("Error:", err)
    } else {
        fmt.Println("Result:", result)
    }
    
    // カスタムエラー
    if err := validateAge(-5); err != nil {
        // 型アサーションでエラーの詳細を取得
        if ve, ok := err.(*ValidationError); ok {
            fmt.Printf("Validation error - Field: %s, Message: %s\n", ve.Field, ve.Message)
        }
    }
    
    // エラーのラップと展開
    err = processFile("test.txt")
    if err != nil {
        fmt.Println(err)
        
        // errors.Is でエラーを比較
        // errors.As でエラーの型を取得
    }
}

並行処理

goroutine

package main

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

func sayHello(name string) {
    for i := 0; i < 3; i++ {
        fmt.Printf("Hello, %s! (%d)\n", name, i)
        time.Sleep(100 * time.Millisecond)
    }
}

func main() {
    // goroutineの起動
    go sayHello("Alice")
    go sayHello("Bob")
    
    // メインgoroutineが終了するとすべて終了
    time.Sleep(time.Second)
    
    // WaitGroup で待機
    var wg sync.WaitGroup
    
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func(n int) {
            defer wg.Done()
            fmt.Printf("Worker %d started\n", n)
            time.Sleep(100 * time.Millisecond)
            fmt.Printf("Worker %d done\n", n)
        }(i)
    }
    
    wg.Wait()
    fmt.Println("All workers completed")
}

チャネル

package main

import (
    "fmt"
    "time"
)

func main() {
    // チャネルの作成
    ch := make(chan int)
    
    // 送信(別のgoroutineで)
    go func() {
        ch <- 42
    }()
    
    // 受信
    value := <-ch
    fmt.Println(value)  // 42
    
    // バッファ付きチャネル
    buffered := make(chan int, 3)
    buffered <- 1
    buffered <- 2
    buffered <- 3
    // buffered <- 4  // ブロックされる(バッファフル)
    
    fmt.Println(<-buffered)  // 1
    fmt.Println(<-buffered)  // 2
    
    // チャネルのクローズ
    close(buffered)
    
    // クローズされたチャネルからの読み取り
    value, ok := <-buffered
    fmt.Println(value, ok)  // 3 true
    
    value, ok = <-buffered
    fmt.Println(value, ok)  // 0 false(クローズ済み)
    
    // rangeでチャネルを読み取り
    numbers := make(chan int)
    go func() {
        for i := 0; i < 5; i++ {
            numbers <- i
        }
        close(numbers)
    }()
    
    for n := range numbers {
        fmt.Println(n)
    }
}

// 送信専用チャネル
func send(ch chan<- int, value int) {
    ch <- value
}

// 受信専用チャネル
func receive(ch <-chan int) int {
    return <-ch
}

select

package main

import (
    "fmt"
    "time"
)

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)
    
    go func() {
        time.Sleep(100 * time.Millisecond)
        ch1 <- "one"
    }()
    
    go func() {
        time.Sleep(200 * time.Millisecond)
        ch2 <- "two"
    }()
    
    // select で複数のチャネルを待機
    for i := 0; i < 2; i++ {
        select {
        case msg1 := <-ch1:
            fmt.Println("Received:", msg1)
        case msg2 := <-ch2:
            fmt.Println("Received:", msg2)
        }
    }
    
    // タイムアウト
    ch := make(chan int)
    
    select {
    case v := <-ch:
        fmt.Println("Received:", v)
    case <-time.After(time.Second):
        fmt.Println("Timeout!")
    }
    
    // 非ブロッキング
    select {
    case v := <-ch:
        fmt.Println("Received:", v)
    default:
        fmt.Println("No value available")
    }
}

並行パターン

Worker Pool

package main

import (
    "fmt"
    "sync"
)

func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
    defer wg.Done()
    for job := range jobs {
        fmt.Printf("Worker %d processing job %d\n", id, job)
        results <- job * 2
    }
}

func main() {
    numJobs := 10
    numWorkers := 3
    
    jobs := make(chan int, numJobs)
    results := make(chan int, numJobs)
    
    var wg sync.WaitGroup
    
    // ワーカーを起動
    for w := 1; w <= numWorkers; w++ {
        wg.Add(1)
        go worker(w, jobs, results, &wg)
    }
    
    // ジョブを送信
    for j := 1; j <= numJobs; j++ {
        jobs <- j
    }
    close(jobs)
    
    // ワーカーの完了を待機
    go func() {
        wg.Wait()
        close(results)
    }()
    
    // 結果を収集
    for result := range results {
        fmt.Println("Result:", result)
    }
}

Fan-out / Fan-in

package main

import (
    "fmt"
    "sync"
)

func generator(nums ...int) <-chan int {
    out := make(chan int)
    go func() {
        for _, n := range nums {
            out <- n
        }
        close(out)
    }()
    return out
}

func square(in <-chan int) <-chan int {
    out := make(chan int)
    go func() {
        for n := range in {
            out <- n * n
        }
        close(out)
    }()
    return out
}

func merge(cs ...<-chan int) <-chan int {
    var wg sync.WaitGroup
    out := make(chan int)
    
    output := func(c <-chan int) {
        defer wg.Done()
        for n := range c {
            out <- n
        }
    }
    
    wg.Add(len(cs))
    for _, c := range cs {
        go output(c)
    }
    
    go func() {
        wg.Wait()
        close(out)
    }()
    
    return out
}

func main() {
    in := generator(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
    
    // Fan-out: 複数のgoroutineで処理
    c1 := square(in)
    c2 := square(in)
    c3 := square(in)
    
    // Fan-in: 結果をマージ
    for n := range merge(c1, c2, c3) {
        fmt.Println(n)
    }
}

Context によるキャンセル

package main

import (
    "context"
    "fmt"
    "time"
)

func doWork(ctx context.Context, id int) {
    for {
        select {
        case <-ctx.Done():
            fmt.Printf("Worker %d cancelled: %v\n", id, ctx.Err())
            return
        default:
            fmt.Printf("Worker %d working...\n", id)
            time.Sleep(500 * time.Millisecond)
        }
    }
}

func main() {
    // タイムアウト付きコンテキスト
    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel()
    
    go doWork(ctx, 1)
    go doWork(ctx, 2)
    
    // タイムアウトを待機
    <-ctx.Done()
    fmt.Println("Main: all workers should be cancelled")
    
    time.Sleep(time.Second)  // ワーカーの終了を待機
}

パッケージとモジュール

プロジェクト構成

myproject/
├── go.mod
├── go.sum
├── main.go
├── cmd/
│   └── server/
│       └── main.go
├── internal/
│   ├── config/
│   │   └── config.go
│   ├── handler/
│   │   └── handler.go
│   └── service/
│       └── service.go
├── pkg/
│   └── utils/
│       └── utils.go
└── api/
    └── routes.go
// go.mod
module github.com/username/myproject

go 1.21

require (
    github.com/gin-gonic/gin v1.9.1
    github.com/lib/pq v1.10.9
)
# 依存関係の追加
go get github.com/gin-gonic/gin

# 依存関係の整理
go mod tidy

# ベンダリング
go mod vendor

テスト

// calculator.go
package calculator

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

func Divide(a, b int) (int, error) {
    if b == 0 {
        return 0, errors.New("division by zero")
    }
    return a / b, nil
}
// calculator_test.go
package calculator

import (
    "testing"
)

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

// テーブル駆動テスト
func TestAddTableDriven(t *testing.T) {
    tests := []struct {
        name     string
        a, b     int
        expected int
    }{
        {"positive numbers", 2, 3, 5},
        {"negative numbers", -2, -3, -5},
        {"mixed numbers", -2, 3, 1},
        {"zeros", 0, 0, 0},
    }
    
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            result := Add(tt.a, tt.b)
            if result != tt.expected {
                t.Errorf("Add(%d, %d) = %d; expected %d", tt.a, tt.b, result, tt.expected)
            }
        })
    }
}

// ベンチマーク
func BenchmarkAdd(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Add(2, 3)
    }
}
# テストの実行
go test ./...

# 詳細表示
go test -v ./...

# カバレッジ
go test -cover ./...
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out

# ベンチマーク
go test -bench=. ./...

まとめ

機能 説明
goroutine 軽量スレッド
channel goroutine間の通信
select 複数チャネルの待機
sync.WaitGroup goroutineの完了待機
sync.Mutex 排他制御
context キャンセル・タイムアウト

Goのシンプルさと強力な並行処理機能を活かして、効率的なプログラムを書きましょう!

9
0
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
9
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?