Golang has good concurrency support using goroutines. Goroutines are lightweight threads managed by the Go runtime. Goroutines are multiplexed to fewer OS threads.

Goroutines are non-blocking, and they are not scheduled by the OS. The Go runtime schedules the goroutines.

The scheduler uses a technique called M:N scheduling. M goroutines are multiplexed to N OS threads. The scheduler schedules the goroutines to the OS threads. The scheduler uses a technique called work-stealing to schedule the goroutines

There are entities that can block the execution of goroutines. Let’s see the entities that can block the execution of goroutines.

Channel

Channels are used to communicate between goroutines. Channels can block the execution of goroutines.

When a goroutine sends data to a channel, it blocks until another goroutine receives the data from the channel.

package main

import (
    "fmt"
    "time"
)

func main() {
    // Create a channel
    ch := make(chan int)

    // Launch a goroutine to send data to the channel
    go func() {
        fmt.Println("Sending data to the channel")
        ch <- 42
        fmt.Println("Data sent to the channel")
    }()

    // Simulate some work
    time.Sleep(1 * time.Second)

    // Receive data from the channel
    fmt.Println("Receiving data from the channel")
    data := <-ch
    fmt.Println("Data received from the channel:", data)
}

Here is a table to summarize channel’s behavior based on operations:

Channel state Operation Behavior
empty send blocks
empty receive blocks
full send blocks
full receive unblocks
closed send panics
closed receive returns zero value

WaitGroup

WaitGroup is used to wait for a collection of goroutines to finish. WaitGroup can block the execution of goroutines.

When a goroutine calls WaitGroup.Wait(), it blocks until all goroutines call WaitGroup.Done().

package main

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

func main() {
    // Create a WaitGroup
    var wg sync.WaitGroup

    // Launch multiple goroutines
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(i int) {
            defer wg.Done()
            fmt.Println("Goroutine", i, "started")
            time.Sleep(1 * time.Second)
            fmt.Println("Goroutine", i, "finished")
        }(i)
    }

    // Wait for all goroutines to finish
    wg.Wait()
    fmt.Println("All goroutines finished")
}

Mutex

Mutex is a synchronization primitive that can block the execution of goroutines. Mutex is used to synchronize access to shared resources.

When a goroutine acquires a mutex, it blocks other goroutines from acquiring the mutex. The goroutine that acquires the mutex can access the shared resource.

package main

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

// Shared counter variable
var counter int = 0

// Mutex to protect the counter
var counterMutex sync.Mutex

func main() {
	const numGoroutines = 10

	// A WaitGroup to track goroutines
	var wg sync.WaitGroup

	// Launch multiple goroutines
	for i := 0; i < numGoroutines; i++ {
		wg.Add(1)
		go incrementCounter(&wg)
	}

	// Wait for all goroutines to complete
	wg.Wait()

	fmt.Println("Final counter value:", counter)
}

// Safely increments the counter
func incrementCounter(wg *sync.WaitGroup) {
	defer wg.Done()

	for i := 0; i < 100; i++ {
		counterMutex.Lock()
		counter++
		counterMutex.Unlock()
		time.Sleep(10 * time.Millisecond) // Simulate some work
	}
}

Here’s a table to summarize mutex’s behavior based on operations:

Mutex state Operation Behavior
unlocked lock Acquires the lock
unlocked unlock panics
locked lock blocks
locked unlock Unlocks the mutex