Contents

Concurrency in Go

Syntax

Goroutines are lightweight threads managed by the Go runtime. They allow you to run functions concurrently, making Go highly efficient for parallel processing tasks. Goroutines are cheaper in terms of memory and processing resources compared to traditional threads. They are created using the go keyword before a function call. The Go runtime schedules and manages the execution of goroutines, enabling high concurrency with minimal overhead.

Example:

				
					func printNumbers() {
    for i := 1; i <= 5; i++ {
        fmt.Println(i)
    }
}

func main() {
    go printNumbers()  // This runs concurrently
    fmt.Println("Goroutine started")
    
    // Wait for a while to allow goroutine to complete
    time.Sleep(time.Second)
}
				
			

Channels

Channels are a way to communicate between goroutines. They provide a mechanism for one goroutine to send data to another goroutine in a thread-safe manner. Channels can be used to synchronize tasks, coordinate actions, and pass messages. You can create a channel with a specific type, and then use the <- operator to send and receive data. Channels are a core part of Go’s concurrency model.

Example:

				
					func sum(a, b int, resultChan chan int) {
    resultChan <- a + b  // Send result to channel
}

func main() {
    resultChan := make(chan int)
    go sum(2, 3, resultChan)
    
    result := <-resultChan  // Receive result from channel
    fmt.Println(result)     // Output: 5
}
				
			

Buffered Channels

Buffered channels are channels with a capacity to hold a fixed number of values before any goroutine is blocked on sending or receiving data. They allow for asynchronous communication, meaning a goroutine can send data to a channel without blocking if the buffer is not full. Buffered channels are useful when you want to decouple the timing of sending and receiving operations between goroutines.

Example:

				
					func main() {
    ch := make(chan int, 2)  // Buffered channel with capacity 2

    ch <- 1  // Does not block
    ch <- 2  // Does not block

    fmt.Println(<-ch)  // Output: 1
    fmt.Println(<-ch)  // Output: 2
}
				
			

Select Statement

The select statement in Go is used to handle multiple channel operations. It allows a goroutine to wait on multiple channels and proceed with the first channel that is ready to send or receive data. The select statement is similar to a switch but for channels, and it helps in managing complex communication patterns between goroutines. It ensures that the program does not block on a single channel when there are multiple channels to monitor.

Example:

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

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

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

    select {
    case msg1 := <-ch1:
        fmt.Println(msg1)
    case msg2 := <-ch2:
        fmt.Println(msg2)
    case <-time.After(3 * time.Second):
        fmt.Println("Timeout")
    }
}

				
			

Sync Package

The sync package in Go provides basic synchronization primitives such as mutexes, wait groups, and once initialization. These tools help manage concurrency and ensure safe access to shared data across goroutines. The sync.Mutex type is used to lock critical sections of code to prevent race conditions, while sync.WaitGroup is used to wait for a collection of goroutines to finish executing. sync.Once ensures that a piece of code runs only once, regardless of how many times it is called.

Example (Mutex and WaitGroup):

				
					var (
    counter int
    mutex   sync.Mutex
    wg      sync.WaitGroup
)

func increment() {
    defer wg.Done()
    mutex.Lock()
    counter++
    mutex.Unlock()
}

func main() {
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go increment()
    }

    wg.Wait()
    fmt.Println("Final Counter:", counter)  // Output: Final Counter: 5
}