Skip to content

Latest commit

 

History

History
163 lines (122 loc) · 3.05 KB

5.3.md

File metadata and controls

163 lines (122 loc) · 3.05 KB

Sync

Concepts

Sync is a stdlib package that can simplify complex channel operations. Below, we will implement solutions with solely channels, and then show how we can take advantage of sync. For simple cases the solutions are often similar, but as projects gets more complex, sync will see larger benefits in simplicity and speed.

Mutex

package main
import "os"

func main() {
    lock := make(chan bool, 1)
    for i := 0; i <= 3; i++ {
        go func() {
            lock <- true
            appendToFile()
            <-lock
        }()
    }
}

func appendToFile() {
    f, _ := os.OpenFile("text.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
    defer f.Close()
    f.WriteString("text to append\n")
}

We can instead use a mutex:

...

func main(){
    var lock sync.Mutex
    for i := 0; i <= 3; i++ {
        go func() {
            lock.Lock()
            appendToFile()
            lock.Unlock()
        }
    }
}
...

Although this is a small change, it more clearly describes the behavior of the program.

Once

Once can be helpful if we want to initialize connections but do not know their current state.

package main

import "fmt"

func main() {
    onceChan := make(chan bool, 1)
    onceChan <- true
    init := func() {
        select {
        case <-onceChan:
            fmt.Println("init")
        default:
        }
    }
    for i := 0; i < 10; i++ {
        init()
    }
}

Using Once:

package main

import (
    "fmt"
    "sync"
)

func main() {
    var once sync.Once
    init := func() { fmt.Println("init") }
    for i := 0; i < 10; i++ {
        once.Do(init)
    }
}

WaitGroup

Taking a look at our example from 5.1 and 5.2:

package main

import "fmt"

var saidHi chan bool = make(chan bool, 3)

func main() {
    for _, name := range []string{"Andrew", "Beavis", "Cory"} {
        go sayHi(name)
    }
    for i := 0; i < 3; i++ {
        <-saidHi
    }
}

func sayHi(n string) {
    fmt.Println(n)
    saidHi <- true
}

While we removed our race condition with the above example, we also added a channels to track progress. We can simplify this:

package main

import (
    "fmt"
    "sync"
)

var wg sync.WaitGroup

func main() {
    for _, name := range []string{"Andrew", "Beavis", "Cory"} {
        wg.Add(1) // Adds 1 to the wait group
        go sayHi(name)
    }
    wg.Wait() // Waits until the value is 0
}

func sayHi(n string) {
    fmt.Println(n)
    wg.Done() // Subtracts 1 from the wait group
}

This cleans up a lot of magic numbers and for-loops that aren't entirely obvious.

Exercises

Given a list of 1-100, print batches of 10 concurrently, so that each batch doesn't being printing until the previous batch has finished.

Tips

  • Composing sync.Mutex with structs is very powerful.

Further Reading

sync.Pool lets you use reuse memory without allocating new objects. This is an in depth article that describes how and when to use them.


prev -- up