看一段代码:
package main
import (
"fmt"
"runtime"
"sync"
)
var (
counter int
wg sync.WaitGroup
)
func main() {
wg.Add(2)
go incCounter(1)
go incCounter(2)
wg.Wait()
fmt.Println("Final Counter:", counter)
}
func incCounter(id int) {
defer wg.Done()
for count := 0; count < 2; count++ {
value := id
runtime.Gosched()
value++
counter = value
}
}
goroutine执行的是副本值,然后将副本值写入counter,所以在切换goroutine时,goroutine中的值会覆盖counter。其中Gosched函数是runtime包中用于将goroutine从当前线程退出,给其它goroutine运行的机会。这段代码执行下来理论上应该是存在竞争状态的,对于counter这个变量,在两个goroutine的切换下,一共加了4次,但是由于每次切换后进入队列的并不是真的这个值,而是一个副本,结果输出应该为2。
事实貌似是这样。。。貌似有点小问题。。。
检测竞争状态,再把这个gosched函数注释,然后重新检测竞争状态,先后编译执行得到的是:
为什么会出现这个情况呢?Final Counter: 3
==================
WARNING: DATA RACE
Write at 0x0000005b73c0 by goroutine 6:
main.incCounter()
/home/qmq/gopath/src/github.com/goinaction/code/test.go:49 +0x74
Previous write at 0x0000005b73c0 by goroutine 7:
main.incCounter()
/home/qmq/gopath/src/github.com/goinaction/code/test.go:49 +0x74
Goroutine 6 (running) created at:
main.main()
/home/qmq/gopath/src/github.com/goinaction/code/test.go:25 +0x68
Goroutine 7 (running) created at:
main.main()
/home/qmq/gopath/src/github.com/goinaction/code/test.go:26 +0x89
==================
Final Counter: 2
Found 1 data race(s)
==================
WARNING: DATA RACE
Write at 0x0000005b73c0 by goroutine 7:
main.incCounter()
/home/qmq/gopath/src/github.com/goinaction/code/test.go:49 +0x74
Previous write at 0x0000005b73c0 by goroutine 6:
main.incCounter()
/home/qmq/gopath/src/github.com/goinaction/code/test.go:49 +0x74
Goroutine 7 (running) created at:
main.main()
/home/qmq/gopath/src/github.com/goinaction/code/test.go:26 +0x89
Goroutine 6 (finished) created at:
main.main()
/home/qmq/gopath/src/github.com/goinaction/code/test.go:25 +0x68
==================
Final Counter: 3
Found 1 data race(s)================
输出小概率有3的情况,可能是goroutine没有退出,所以发生了新goroutine中的值与上一次goroutine副本值相加的情况。对于这样存在多个goroutine对一个共享资源进行操作的功能还是需要对其加锁,或使用简单的atomic,以及使用Go的特色通道 。
1.互斥锁
package main
import (
"fmt"
"runtime"
"sync"
)
var (
counter int
wg sync.WaitGroup
//互斥锁
mutex sync.Mutex
)
func main() {
wg.Add(2)
go incCounter(1)
go incCounter(2)
wg.Wait()
fmt.Printf("Final Counter: %d
", counter)
}
func incCounter(id int) {
defer wg.Done()
for count := 0; count < 2; count++ {
//互斥锁锁定的临界代码块,只允许同一时刻只有一个goroutine访问
mutex.Lock()
{ //习惯地加上大括号更清晰
// Capture the value of counter.
value := counter
// Yield the thread and be placed back in queue.
runtime.Gosched()
// Increment our local value of counter.
value++
// Store the value back into counter.
counter = value
}
mutex.Unlock()
//解锁
}
}
2.atomic包
package main
import (
"fmt"
"sync"
"sync/atomic"
"time"
)
var (
shutdown int64
wg sync.WaitGroup
)
func main() {
wg.Add(2)
go doWork("A")
go doWork("B")
//设定goroutine执行的时间
time.Sleep(1 * time.Second)
fmt.Println("Shutdown Now")
//安全标志 判断是否可以停止goroutine工作
atomic.StoreInt64(&shutdown, 1)
wg.Wait()
}
func doWork(name string) {
defer wg.Done()
for {
fmt.Printf("Doing %s Work
", name)
time.Sleep(250 * time.Millisecond)
if atomic.LoadInt64(&shutdown) == 1 {
fmt.Printf("Shutting %s Down
", name)
break
}
}
}
3.通道
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
var wg sync.WaitGroup
func init() {
rand.Seed(time.Now().UnixNano())
}
func main() {
court := make(chan int)
wg.Add(2)
go player("Ding", court)
go player("Sha", court)
court <- 1
wg.Wait()
}
func player(name string, court chan int) {
defer wg.Done()
for {
ball, ok := <-court
if !ok {
fmt.Printf("Player %s Won
", name)
return
}
n := rand.Intn(100)
if n%13 == 0 {
fmt.Printf("Player %s Missed
", name)
close(court)
return
}
fmt.Printf("Player %s Hit %d
", name, ball)
ball++
court <- ball
}
}