Synchronizing goroutines

via sync package

Ways of declaring goroutines

func main() {
    go sayHello()
}

func sayHello() {
    fmt.Println("hello")
}
go func() {
    fmt.Println("hello")
}() // <--- the anonymous function must be invoked immediately
sayHello := func() {
    fmt.Println("hello")
}
go sayHello()

To make sure your goroutines execute before the main goroutine we need join points. These can be created via:

WaitGroup primitive

for waiting for a set of concurrent operations to complete when you either don't care about the result of the concurrent operation, or you have other means of collecting their results

Closures = a function value that references variables from outside its body.

With closures, we'd have to pass a copy of the variable into the closure so by the time a goroutine is run, it will be operating on the data from its iteration of the loop.

Mutex

provides a concurrent-safe way to express exclusive access to these shared resources.

sync.Mutex interface with Lock() and Unlock() methods

RWMutex

same as Mutex but it provides a read/write lock. We can have a multiple number of readers holding a reader lock as long as nobody is holding a writer lock.

sync.RWMutex interface with RLock() and RUnlock() methods

RWMutex can only be held by n readers at a time, or by a single writer

Cond

a rendezvous point for goroutines waiting for or announcing the occurence of an event (=signal between 2 or more goroutines, has no info other than it happened).

sync.NewCond(&sync.Mutex{}) with 2 methods

  • Signal - notifies goroutines (runtime picks the one that has been waiting the longest) blocked on a Wait call that the condition has been triggered

Brodcast - sends signal to all waiting goroutines

Once

ensures that only one call to Do ever calls the function passed in

Pool

= concurrent-safe implementation of the object pool pattern.

Get interface - checks wether the are any available instances within the pool to return to the caller, and if not, call its New member variable to create one.

Put interface - to put the instance they were working with back in the pool

Uses cases:

  • memory optimisations as instantiated objects are garbage collected.

  • warming up a cache of pre-allocated objects for operations that must run as quickly as possible. (by trying to guard the host machine's memory front-loading the time it takes to get a reference to another object)

Channels

can be used to synchronise access of the memory and to communicate information between goroutines.

Instantiating

support unidirectional flow of data:

  • channel that can only read

  • channel that can only send

Sending/Receiving

Closing

Ranging over a channel

Closing a channel signals to multiple goroutines

Buffered Channels

channels that are given a capacity when they're instantiated.

Result of channel operation given a channel's state

Operation
Channel state
Result

Read

nil

block

open and non empty

value

open and empty

block

closed

<default value>, false

write only

compilation error

Write

nil

block

open and non empty

block

open and empty

write value

closed

panic

receive only

compilation error

close

nil

panic

open and non empty

closes channel; reads suceed until channel is drained, then reads produce default value

open and empty

closes channel; reads produces default value

closed

panic

receive only

compilation error

Channel Owners&Consumers

A channel owner should:

  • instantiate the channel.

  • perform writes, or pass ownership to another goroutine.

  • close the channel.

  • encapsulate the previous three things in this list and expose them via a reader channel.

A channel consumer should:

  • knowing when a channel is closed.

  • be responsible for handling blocking for any reason.

Last updated

Was this helpful?