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.
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 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)
}
}
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.
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.
- Composing sync.Mutex with structs is very powerful.
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.