Concurrency in Go

15/8/2025
Concurrency in Go

Here is the content converted into clean, structured Markdown. I have preserved the structure, code blocks, and formatting while ensuring the image placeholders are clear.

Concurrency in Go

Concurrency vs. Parallelism

  • Concurrency is when a program handles multiple tasks at the same time (but not necessarily simultaneously).
  • Parallelism is when tasks are actually run at the same time.

86f48a07 8308 4a25 B818 D978d0da7cab

Go enables concurrency by default, and it can achieve parallelism when running on multicore CPUs.


Goroutines

A goroutine is a function that runs concurrently with others. Every Go program starts with one default goroutine: the main goroutine. When it finishes, all other goroutines are forcibly stopped—even if they’re still working.

Goroutines are lightweight execution threads, created and managed by the Go runtime—not the operating system. This makes them cheaper and faster to work with than traditional OS threads.

Goroutines vs Threads

Aspect Goroutines Threads
Management Go runtime OS
Memory Usage Few KBs Few MBs
Creation cost Extremely low High
Scalability High (millions) Low (limited by OS)

The syntax of goroutines couldn’t be simpler:

go sayHello()

Here’s a basic example:

package main

import (
    "fmt"
    "time"
)

func sayHello() {
    fmt.Println("Hello from goroutine!")
}

func main() {
    go sayHello()
    time.Sleep(time.Second)
}

Why the Sleep? Because the main goroutine may finish before the sayHello() goroutine executes. In real applications, we use sync.WaitGroup instead:

package main

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

func main() {
    var wg sync.WaitGroup

    sayHello("Main")

    wg.Add(1)
    go func() {
        defer wg.Done()
        sayHello("Goroutine")
    }()

    wg.Wait() // Wait for the goroutine to finish
}

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

Channels

A channel is a typed conduit for communication between goroutines. It allows them to send and receive data safely and concurrently without explicit locking. By default, channels are bidirectional, meaning they can be used to both send and receive data.
25b3163e 84df 442e Bd83 275206fffa76

"Do not communicate by sharing memory; instead, share memory by communicating." — Rob Pike

Channel Syntax

Declaration using chan:

var channelName chan Type
// or
channelName := make(chan Type)

To send and receive data through a channel, we use the <- operator. This operator acts like a directional indicator, showing where the data is flowing.

  • Sending data: channelName <- value
  • Receiving data: myVar := <- channelName

Example:

package main

import "fmt"

func main() {
    myChan := make(chan int)

    go func() {
        myChan <- 1
    }()

    fmt.Println(<-myChan)
}

Blocking Behavior

One powerful aspect of channels is that send/receive operations block until the other side is ready. This allows goroutines to communicate in a synchronized way—without locks.

  • If a send occurs and no goroutine is ready to receive, the sending goroutine blocks.
  • If a receive occurs and no data is available, the receiving goroutine blocks.

3876deac 0b25 4f2e 9180 776d217d1e11

Example: Deadlock

package main

func main() {
    myChan := make(chan int)
    myChan <- 1 // deadlock here: no one is receiving
    fmt.Println(<-myChan)
}

✅ Fix: Add a Receiver Goroutine

package main

import "fmt"

func main() {
    myChan := make(chan int)

    go func() {
        myChan <- 1
    }()

    fmt.Println(<-myChan)
}

Real-World Use Cases of Goroutines

  • Handling concurrent HTTP requests in a web server.
  • Running parallel tasks in cloud services.
  • Building workers/consumers in a message queue system.
  • Performing asynchronous I/O.
  • Background jobs and cron tasks.
  • Real-time data pipelines.

Go is widely adopted in cloud-native development (e.g., Docker, Kubernetes) because:

  • Goroutines scale extremely well under high load.
  • Fast compilation and small binaries.
  • Built-in concurrency support.
  • Powerful standard library.
  • Excellent support for networking and APIs.

Conclusion

Go makes concurrency simple yet powerful with its goroutines and channels. By abstracting complex thread management behind a clean syntax, Go empowers developers to write efficient, scalable, and readable concurrent code with ease. Mastering Go’s concurrency model is a must-have skill for modern backend development.

Dropdown icon

Blog liên quan

Dropdown icon
Contact Us