For the past few weeks I've been wrestling with one (not-so) simple question:
sync.Mutex
and, conversely, when is it best use a chan
?It seems that for a lot of problems either strategy is interchangeable with the other - and that's just the problem!
Take this video found in the Golang documentation. Below, I've taken the liberty to dictate the code in the playground and also translate it to a sync.Mutex
equivalent.
Notes:
chan
implementation does more work in the same time (reaches 12)*Playgrounds:
Ping/pong with chan
:
package main
import (
"fmt"
"time"
)
type Ball struct { hits int }
func main() {
table := make(chan *Ball)
go player("ping", table)
go player("pong", table)
table <- new(Ball)
time.Sleep(1 * time.Second)
<-table
}
func player(name string, table chan *Ball) {
for {
ball := <-table
ball.hits++
fmt.Println(name, ball.hits)
time.Sleep(100 * time.Millisecond)
table <- ball
}
}
Ping/pong with sync.Mutex
:
package main
import (
"fmt"
"time"
"sync"
)
type Ball struct { hits int }
var m = sync.Mutex{}
func main() {
ball := new(Ball)
go player("ping", ball)
go player("pong", ball)
time.Sleep(1 * time.Second)
}
func player(name string, ball *Ball) {
for {
m.Lock()
ball.hits++
fmt.Println(name, ball.hits)
time.Sleep(100 * time.Millisecond)
m.Unlock()
}
}
In some case Channels are best, in some sync.Mutex
, for this simple example sync.Mutex
is 10x slower than channel:
e.g. here the difference is 101 million (Imagine you need One second counter):
1- Consider this simple code:
package main
import (
"sync"
"time"
)
func main() {
var i rwm
go func() {
for {
i.inc() // free running counter
}
}()
time.Sleep(1 * time.Second)
println(i.read()) // sampling the counter
}
type rwm struct {
sync.RWMutex
i int
}
func (l *rwm) inc() {
l.Lock()
defer l.Unlock()
l.i++
}
func (l *rwm) read() int {
l.RLock()
defer l.RUnlock()
return l.i
}
output:
11227280
2- versus this using channel:
package main
import "time"
func main() {
ch := make(chan int)
go func() {
timeout := time.NewTimer(1 * time.Second)
defer timeout.Stop()
i := 1
for {
select {
case <-timeout.C:
ch <- i
return
default:
i++
}
}
}()
println(<-ch)
}
output:
112362269
difference:
112362269-11227280=101134989
112362269/11227280=10x