search

Explain the use of Go's wait groups and semaphores for synchronizing and coordinating multiple goroutines in Go programs?

The use of Go's wait groups and semaphores for synchronizing and coordinating multiple goroutines in Go programs.

In Go, a wait group is a synchronization primitive that allows a program to wait for a group of goroutines to complete before continuing execution. A wait group is created using the **sync.WaitGroup** struct, and its **Add**, **Done**, and **Wait** methods are used to coordinate the execution of multiple goroutines.

The **Add** method is used to add the number of goroutines that the wait group will wait for. The **Done** method is called by each goroutine to signal that it has completed its work. The **Wait** method blocks until the count of the wait group has been reduced to zero by calls to **Done**.

For example, consider the following code:

package main

import (
    "fmt"
    "sync"
)

func worker(id int, wg *sync.WaitGroup) {
    defer wg.Done()
    fmt.Printf("Worker %d starting\n", id)
    // Do some work here
    fmt.Printf("Worker %d done\n", id)
}

func main() {
    var wg sync.WaitGroup
    for i := 1; i <= 5; i++ {
        wg.Add(1)
        go worker(i, &wg)
    }
    wg.Wait()
    fmt.Println("All workers done")
}

In this code, we create a wait group and add 5 goroutines to it using the **Add** method. Each goroutine calls the **worker** function, which simulates doing some work and then calls **Done** to signal that it has completed. Finally, the main function waits for all the workers to finish using the **Wait** method.

A semaphore is another synchronization primitive that is used to limit the number of goroutines that can access a shared resource at the same time. A semaphore is created using the **sync.Mutex** or **sync.RWMutex** struct, and its **Lock** and **Unlock** methods are used to control access to the shared resource.

For example, consider the following code:

package main

import (
    "fmt"
    "sync"
)

type Counter struct {
    value int
    mutex sync.Mutex
}

func (c *Counter) Increment() {
    c.mutex.Lock()
    defer c.mutex.Unlock()
    c.value++
}

func (c *Counter) Decrement() {
    c.mutex.Lock()
    defer c.mutex.Unlock()
    c.value--
}

func main() {
    var wg sync.WaitGroup
    var sem = make(chan struct{}, 3)
    var counter Counter

    for i := 1; i <= 10; i++ {
        wg.Add(1)
        sem <- struct{}{}
        go func() {
            defer wg.Done()
            counter.Increment()
            fmt.Printf("Counter value: %d\n", counter.value)
            <-sem
        }()
    }

    wg.Wait()
}

In this code, we create a counter that is protected by a mutex. We also create a channel with a buffer of 3 elements, which will act as a semaphore to limit the number of goroutines that can access the counter at the same time. Each goroutine that accesses the counter first acquires a semaphore token by sending an empty struct to the channel. Then it increments the counter, prints its value, and releases the semaphore token by receiving an empty struct from the channel.

By limiting the number of goroutines that can access the counter at the same time, we ensure that the counter is accessed in a thread-safe manner and that its value is consistent.

Related Questions You Might Be Interested