Skip to main content

Concurrent Constructs

Wait Groups

The sync package provides the WaitGroup type for synchronizing goroutines. The syntax for declaring a WaitGroup is as follows:

var wg sync.WaitGroup

The Add method is used to increment the counter of the WaitGroup.

wg.Add(1)

The Done method is used to decrement the counter of the WaitGroup.

wg.Done()

The Wait method is used to block until the counter of the WaitGroup becomes zero.

wg.Wait()

The following example demonstrates the use of a WaitGroup to synchronize goroutines.

package main

import (
"fmt"
"sync"
)

func worker(id int32, wg sync.WaitGroup) {
fmt.Println("Worker", id, "starting")
wg.Done()
}

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")
}

Mutex

The sync package also provides the Mutex type for synchronizing access to shared variables. The syntax for declaring a mutex is as follows:

var m sync.Mutex

The Lock and Unlock methods are used to lock and unlock the mutex, respectively.

m.Lock()
// code
m.Unlock()

The TryLock method is used to try to lock the mutex without blocking.

if m.TryLock() {
// code
m.Unlock()
}

The following example demonstrates the use of a mutex to synchronize access to a shared variable.

  • Before synchronization:

    package main

    import (
    "fmt"
    "sync"
    )

    var count int32
    var wg sync.WaitGroup

    func increment() {
    for i := 0; i < 100; i++ {
    count++
    }
    wg.Done()
    }

    func main() {
    wg.Add(2)
    go increment()
    go increment()
    wg.Wait()
    fmt.Println(count) // prints 100
    }
  • After synchronization:

    package main

    import (
    "fmt"
    "sync"
    )

    var count int32
    var wg sync.WaitGroup
    var m sync.Mutex

    func increment() {
    for i := 0; i < 100; i++ {
    m.Lock()
    count++
    m.Unlock()
    }
    wg.Done()
    }

    func main() {
    wg.Add(2)
    go increment()
    go increment()
    wg.Wait()
    fmt.Println(count) // prints 200
    }

Channels

The syntax for declaring a channel is as follows:

unbuffered := make(chan int32) // unbuffered channel
buffered := make(chan int32, 5) // buffered channel

The send and receive operations are used to send and receive data through a channel, respectively.

unbuffered <- 5 // send
x := <-unbuffered // receive

The following example demonstrates the use of channels to synchronize goroutines.

package main

import (
"fmt"
"sync"
)

var wg sync.WaitGroup

func producer(ch chan int32) {
for i := 0; i < 5; i++ {
// the producer gets blocked if there is no consumer
ch <- i
}
fmt.Println("Producer done") // this does not get printed
// making the channel buffered will allow the producer to continue
// by changing the channel declaration to "ch := make(chan int32, 1)"
wg.Done()
}

func consumer(ch chan int32) {
for i := 0; i < 4; i++ {
fmt.Println(<-ch)
}
fmt.Println("Consumer done")
wg.Done()
}

func main() {
wg.Add(2)
ch := make(chan int32)
go producer(ch)
go consumer(ch)
wg.Wait()
}
note

We do not support closing channels in the current version of the language.

Select

The select statement is used to wait on multiple communication operations. The syntax for the select statement is as follows:

select {
case x := <-ch1:
// code
case ch2 <- y:
// code
default:
// code
}

The select statement blocks until one of the cases can proceed. If multiple cases can proceed, one is chosen at random.