假设在一台4核CPU, 64位系统机子上运行以下程序输出什么?
func main() {
runtime.GOMAXPROCS(0)
wg := sync.WaitGroup{}
goRoutineCount := runtime.NumCPU()
wg.Add(goRoutineCount)
var loopNum uint64 = 100000000
var x uint64 = 0
for i := 0; i < goRoutineCount; i++ {
go func() {
for n := uint64(0); n < loopNum; n++ {
x += 1
}
wg.Done()
}()
}
wg.Wait()
expectedNumber := uint64(goRoutineCount) * uint64(loopNum)
if x != expectedNumber {
fmt.Printf("错误\n")
}else {
fmt.Printf("正确\n")
}
}
请思考…
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
错误
请思考原因…
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
原因: 线程同步问题, go普通类型的操作不是原子性的 , 请思考如何解决?
,
,
,
,
,
,
,
,
,
,
,
,
,
解决方案:
- 1.使用同步原语,如
sync.Mutex
- 2.使用
channel
进行同步 - 3.使用原子操作 , 如
sync/atomic
- 4.其他同步操作
请自行实现方案1, 2, 3, 并运行…
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
使用原子操作:
func main() {
runtime.GOMAXPROCS(0)
wg := sync.WaitGroup{}
goRoutineCount := runtime.NumCPU()
wg.Add(goRoutineCount)
var loopNum uint64 = 100000
var x uint64 = 0
for i := 0; i < goRoutineCount; i++ {
go func() {
for n := uint64(0); n < loopNum; n++ {
atomic.AddUint64(&x, 1)
}
wg.Done()
}()
}
wg.Wait()
expectedNumber := uint64(goRoutineCount) * uint64(loopNum)
if x != expectedNumber {
fmt.Printf("错误\n")
} else {
fmt.Printf("正确\n")
}
}
使用mutex:
type MyCounter struct {
sync.Mutex
x uint64
}
func NewMyCounter(n uint64) *MyCounter {
return &MyCounter{
x: n,
}
}
func (cnt *MyCounter) Add(n uint64) {
cnt.Lock()
defer cnt.Unlock()
cnt.x += 1
}
func (cnt *MyCounter) Get() uint64 {
return cnt.x
}
func main() {
runtime.GOMAXPROCS(0) // CPU数
wg := sync.WaitGroup{}
goRoutineCount := runtime.NumCPU()
wg.Add(goRoutineCount)
var loopNum uint64 = 100000
// var x uint64 = 0
x := NewMyCounter(0)
for i := 0; i < goRoutineCount; i++ {
go func() {
for n := uint64(0); n < loopNum; n++ {
x.Add(1)
}
wg.Done()
}()
}
wg.Wait()
expectedNumber := uint64(goRoutineCount) * uint64(loopNum)
if x.Get() != expectedNumber {
fmt.Printf("错误\n")
} else {
fmt.Printf("正确\n")
}
}
使用channel:
func main() {
runtime.GOMAXPROCS(0) // CPU数
wg := sync.WaitGroup{}
goRoutineCount := runtime.NumCPU()
wg.Add(goRoutineCount)
var loopNum uint64 = 100000
var x uint64 = 0
// 思考: 如果改称 make(chan bool) 或 make(chan bool, 0)是否可以? make(chan bool, 10000)呢?
ch := make(chan bool, 1)
ch <- true
for i := 0; i < goRoutineCount; i++ {
go func() {
for n := uint64(0); n < loopNum; n++ {
<-ch
x += 1
ch <- true
}
wg.Done()
}()
}
wg.Wait()
expectedNumber := uint64(goRoutineCount) * uint64(loopNum)
if x != expectedNumber {
fmt.Printf("错误\n")
} else {
fmt.Printf("正确\n")
}
}
思考: 用以下方式可以吗? 为什么?
func main() {
runtime.GOMAXPROCS(0) // CPU数
wg := sync.WaitGroup{}
goRoutineCount := runtime.NumCPU()
wg.Add(goRoutineCount)
var loopNum uint64 = 100000
var x uint64 = 0
var flag bool = false
for i := 0; i < goRoutineCount; i++ {
go func() {
for n := uint64(0); n < loopNum; n++ {
for !flag {
} // 等待flag
flag = false
x += 1
flag = true
}
wg.Done()
}()
}
flag = true
wg.Wait()
expectedNumber := uint64(goRoutineCount) * uint64(loopNum)
if x != expectedNumber {
fmt.Printf("错误\n")
} else {
fmt.Printf("正确\n")
}
}
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
在Go语言中,同一个Goroutine线程内部,顺序一致性内存模型是得到保证的。但是不同的Goroutine之间,并不满足顺序一致性内存模型,需要通过明确定义的同步事件来作为同步的参考。如果两个事件不可排序,那么就说这两个事件是并发的。为了最大化并行,Go语言的编译器和处理器在不影响上述规定的前提下可能会对执行语句重新排序(CPU也会对一些指令进行乱序执行)。