The following is loosely based on Go in Practice (page 81):
$ cat simple_locking_with_buffered_channels.go
package main
import(
"fmt"
"time"
)
func main(){
reap := 0; sow := 0
lock := make(chan bool,4100)
for i:=0; i<4001; i++{
go worker(i, lock, &reap)
sow += 1
}
for reap != sow {
fmt.Println("*yawn*")
time.Sleep(100 * time.Millisecond)
}
close(lock)
}
func worker(i int, lock chan bool, reap *int){
fmt.Printf("%d wants the lock
", i)
lock <-true // we acquire the lock thusly.
fmt.Printf("%d has the lock
", i)
time.Sleep(500 * time.Millisecond)
fmt.Printf("%d is releasing the lock
", i)
*reap += 1
<-lock // release
}
When I run it, most of the time it finishes, but occasionally I see that it spins on yawn - perpetually so, until it is killed. Yes, I can add a timeout logic, but I want to know why is this happening.
$ ps -p `pgrep simple_locking` -o lstart,etime
STARTED ELAPSED
Sun Jul 8 11:34:59 2018 02:41
$ ps -p `pgrep simple_locking` -o lstart,etime
STARTED ELAPSED
Sun Jul 8 11:34:59 2018 03:24
It is supposed to work, then why is the odd behavior happening. in those cases, why is my reap != sow ?
~/golearn $ go version
go version go1.10.3 linux/amd64
I am running this on an busy old-ish linux laptop, I am baffled why does it spin intermittently? Thanks in advance!
https://play.golang.org/p/BJwAmRf1OXB
update : 1
I changed the code to use mutex (or so I think..) as:
package main
import(
"fmt"
"time"
"sync"
)
var mutex sync.Mutex
func main(){
reap := 0; sow := 0
lock := make(chan bool,400)
for i:=0; i<389; i++{
go worker(i, lock, &reap)
sow += 1
}
time.Sleep(100 * time.Millisecond)
for reap != sow {
fmt.Println("*yawn*")
time.Sleep(100 * time.Millisecond)
}
close(lock)
}
func worker(i int, lock chan bool, reap *int){
fmt.Printf("%d wants the lock
", i)
lock <-true // we acquire the lock thusly.
fmt.Printf("%d has the lock
", i)
time.Sleep(500 * time.Millisecond)
fmt.Printf("%d is releasing the lock
", i)
mutex.Lock()
*reap += 1
mutex.Unlock()
<-lock // release
}
Is this the right way, since go run --race
still says WARNING: DATA RACE
?
*update 3: *
After trying go's atomic counters, which require delays between increments, I ended up using mutex. What I learned was : even reading (as opposed to writing) can make it complain of race conditions. So here, I wrapped my call in a function call which uses mutex to read, and it clears the --race tests:
$ cat improv.go
package main
import(
"fmt"
"time"
"sync"
)
var mutex sync.Mutex
func main(){
sow := 0
reap := 0
lock := make(chan bool,40)
for i:=0; i<38; i++{
go worker(i, lock, &reap)
sow += 1
}
time.Sleep(100 * time.Millisecond)
//for get_counter(&reap) != get_counter(&sow) {
for get_counter(&reap) != sow {
fmt.Println("*yawn*")
time.Sleep(100 * time.Millisecond)
}
}
func worker(i int, lock chan bool, reap *int){
fmt.Printf("%d wants the lock
", i)
lock <-true // we acquire the lock thusly.
fmt.Printf("%d has the lock
", i)
time.Sleep(500 * time.Millisecond)
fmt.Printf("%d is releasing the lock
", i)
mutex.Lock()
defer mutex.Unlock()
*reap += 1
<-lock // release
}
func get_counter(counter *int) int {
mutex.Lock()
defer mutex.Unlock()
return *counter
}
$ go run --race improv.go >/dev/null