Language
日本語
English

Caution

JavaScript is disabled in your browser.
This site uses JavaScript for features such as search.
For the best experience, please enable JavaScript before browsing this site.

  1. Home
  2. Go Dictionary
  3. goroutine

goroutine

Since: Go 1.0(2012)

A goroutine is a lightweight thread provided by Go. By placing the go keyword before a function call, you can run that function concurrently in a separate execution flow.

Syntax

go functionName(args)

// Launches an anonymous function as a goroutine.
go func() {
    // Code that runs concurrently.
}()

Goroutines are very lightweight — you can launch thousands to millions simultaneously. However, all goroutines are terminated when main() exits.

Goroutine characteristics

FeatureDescription
LightweightFar lighter than OS threads. The initial stack size is only a few KB, and grows dynamically as needed.
Managed by the Go runtimeThe Go runtime maps goroutines onto OS threads. It efficiently schedules N goroutines across M OS threads (the M:N model).
Asynchronous executionA function launched with go returns control immediately. The caller does not wait for the goroutine to finish.
Waiting for completionUse a channel or sync.WaitGroup to wait for a goroutine to complete.
Panic propagationA panic inside a goroutine does not propagate to the caller. Each goroutine must handle panics individually with recover().

Sample code

goroutine.go
package main

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

// worker performs a task and signals completion via WaitGroup.
func worker(id int, wg *sync.WaitGroup) {
    defer wg.Done() // Notifies the WaitGroup when the work is done.
    fmt.Printf("Worker %d: started\n", id)
    time.Sleep(time.Millisecond * 100) // Simulates a time-consuming task.
    fmt.Printf("Worker %d: done\n", id)
}

func main() {
    // Launching a simple goroutine.
    go fmt.Println("Output from another goroutine") // Runs asynchronously.

    // Use WaitGroup to wait for goroutines to finish.
    var wg sync.WaitGroup

    // Launch 5 goroutines.
    for i := 1; i <= 5; i++ {
        wg.Add(1) // Increment the counter before launching the goroutine.
        go worker(i, &wg)
    }

    wg.Wait() // Block until all goroutines have finished.
    fmt.Println("All workers completed")

    // Launching anonymous functions as goroutines.
    // Pass the loop variable as an argument to capture its value correctly.
    var wg2 sync.WaitGroup
    results := make([]int, 5)
    for i := 0; i < 5; i++ {
        wg2.Add(1)
        go func(idx int) {
            defer wg2.Done()
            results[idx] = idx * idx // Each goroutine processes its own index independently.
        }(i) // Pass the loop variable i as an argument.
    }
    wg2.Wait()
    fmt.Println("Squared results:", results) // Prints '[0 1 4 9 16]'.
}

This produces the following output:

go run goroutine.go
Output from another goroutine
Worker 1: started
Worker 3: started
Worker 2: started
Worker 5: started
Worker 4: started
Worker 1: done
Worker 3: done
Worker 2: done
Worker 5: done
Worker 4: done
All workers completed
Squared results: [0 1 4 9 16]

The order of goroutine execution may vary between runs. The output above is just one example.

Communicating between goroutines with channels

Use channels to pass data between goroutines. Go's philosophy is "do not communicate by sharing memory; instead, share memory by communicating." Passing data through channels is generally preferred over protecting shared variables with a Mutex.

sample_goroutine_channel.go
package main

import (
    "fmt"
    "sync"
)

// Processes a job and sends the result to the channel
func processJob(id int, input string, result chan<- string, wg *sync.WaitGroup) {
    defer wg.Done()
    result <- fmt.Sprintf("Job%d: %s processed", id, input)
}

func main() {
    jobs := []string{"Kiryu", "Majima", "Saejima", "Akiyama", "Shinada"}
    results := make(chan string, len(jobs)) // buffered channel

    var wg sync.WaitGroup
    for i, job := range jobs {
        wg.Add(1)
        go processJob(i+1, job, results, &wg)
    }

    // Close the channel after all goroutines finish
    go func() {
        wg.Wait()
        close(results)
    }()

    // Read results until the channel is closed
    for r := range results {
        fmt.Println(r)
    }
}

This produces the following output:

go run sample_goroutine_channel.go
Job1: Kiryu processed
Job3: Saejima processed
Job2: Majima processed
Job4: Akiyama processed
Job5: Shinada processed

Goroutines run concurrently, so the order of output may vary between runs.

Cancellation with context

To cancel a long-running goroutine from outside, use context.WithCancel or context.WithTimeout. If goroutines are not designed to be cancellable, they can leak — continuing to run even after the process should have stopped.

sample_goroutine_context.go
package main

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

// A goroutine that monitors until the context is cancelled
func monitor(ctx context.Context, name string) {
    for {
        select {
        case <-ctx.Done():
            fmt.Printf("%s: stopping (reason: %v)\n", name, ctx.Err())
            return
        default:
            fmt.Printf("%s: monitoring...\n", name)
            time.Sleep(100 * time.Millisecond)
        }
    }
}

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
    defer cancel() // Always defer cancel() to release resources

    go monitor(ctx, "Kiryu")

    // Wait until the context is cancelled
    <-ctx.Done()
    fmt.Println("Main: processing finished")
    time.Sleep(100 * time.Millisecond) // Wait for the goroutine's final log
}

This produces the following output:

go run sample_goroutine_context.go
Kiryu: monitoring...
Kiryu: monitoring...
Main: processing finished
Kiryu: stopping (reason: context deadline exceeded)

The output may vary depending on goroutine scheduling and timing. The number of "monitoring..." lines may differ, and the "stopping (reason: ...)" line may not appear at all in some environments.

Common Mistakes

Common mistake 1: capturing loop variables

Capturing a loop variable directly in a closure causes all goroutines to reference the final value in Go versions below 1.22.

package main

import "sync"

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 3; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            // fmt.Println(i) // Below Go 1.22, all goroutines may print "3"
        }()
    }
    wg.Wait()
}

OK: Pass the variable as an argument to create a copy (safe for Go < 1.22).

sample_goroutine_mistakes.go
package main

import (
    "fmt"
    "sync"
)

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 3; i++ {
        wg.Add(1)
        go func(n int) {
            defer wg.Done()
            fmt.Printf("goroutine %d\n", n)
        }(i) // A copy of i is passed here
    }
    wg.Wait()

}

This produces the following output:

go run sample_goroutine_mistakes.go
goroutine 0
goroutine 1
goroutine 2

Common mistake 2: passing WaitGroup by value

Passing a WaitGroup by value copies it, causing a separate counter. Always pass a pointer (&wg). Also, always call wg.Add(1) before launching the goroutine — calling it inside the goroutine can allow wg.Wait() to return early.

Overview

Goroutines are the foundation of concurrency in Go. Unlike OS threads, they are extremely lightweight, allowing thousands to millions of goroutines to run simultaneously. However, when goroutines share data, you must synchronize access to avoid race conditions.

When launching goroutines inside a loop, capturing the loop variable directly in a closure causes all goroutines to reference the same final value — a common bug. Always pass the variable as an argument instead.

To wait for goroutines to finish, use either sync.WaitGroup or a channel. For safe access to shared data, use sync.Mutex.

If you find any errors or copyright issues, please .