Reputation: 587
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
and struggle to think of a more elegant implementation using sync.Mutex
.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()
}
}
Upvotes: 28
Views: 25311
Reputation: 16233
Some example channel usecases:
Some example primitives usecases:
sync.Mutex
, or sync.RWMutex
)To make it clear, imagine we need a one second counter, so in the following examples we count for a second then print the counter value to see how fast it counts:
No | Count | Method
------------------------------------------------------
1 | 17_729_027 | Using sync.RWMutex for increment
2 | 12_180_741 | Using channel for increment
3 | 106_743_095 | Using channel for timer
4 | 104_178_671 | Using time.AfterFunc and channel sync
Note: go version go1.13.5 linux/amd64
Codes:
1 - Using sync.RWMutex
for increment:
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
}
2 - Using channel for increment:
package main
import (
"time"
)
func main() {
ch := make(chan int, 1)
ch <- 1
timeout := time.NewTimer(1 * time.Second)
loop:
for {
select {
case <-timeout.C:
timeout.Stop()
break loop
default:
ch <- 1 + <-ch
}
}
println(<-ch)
}
3 - Using channel for timer:
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)
}
4 - Using time.AfterFunc
and channel sync:
package main
import (
"fmt"
"time"
)
func main() {
d := 1 * time.Second
i := uint64(0)
ch := make(chan struct{})
time.AfterFunc(d, func() {
close(ch)
})
loop:
for {
select {
case <-ch:
break loop
default:
i++
}
}
fmt.Println(i) // 104_178_671
}
Upvotes: 3
Reputation: 1432
In Go, channels are fantastic, and you can use them to communicate between goroutines. However, you may want to use the sync.Mutex
in some circumstances for convenience.
These circumstances are like the following:
Here are three examples and explanations
Upvotes: 18