坑点
坑点 1:将 wg.add 放在 goroutine 中
请看下列代码
package main
import (
"fmt"
"sync"
)
func main() {
for i := 0; i < 10; i++ {
a := make([]int, 10)
w := sync.WaitGroup{}
for i := 0; i < 10; i++ {
ii := i
go func() {
w.Add(1)
a[ii] = 1
w.Done()
}()
}
w.Wait()
fmt.Println(a)
}
}
结果如下:
[1 1 1 1 1 1 1 1 1 1]
[1 1 1 1 1 1 1 1 1 0]
[1 1 1 1 1 1 1 1 1 1]
[1 1 1 1 1 1 1 1 1 0]
[1 1 1 1 1 1 1 0 0 0]
[1 1 1 1 1 1 1 1 1 0]
[1 1 1 1 1 1 1 0 0 0]
[1 1 1 1 1 1 1 1 1 0]
[1 1 1 1 1 1 1 0 0 0]
[1 1 1 1 1 1 1 1 1 0]
不难发现,有许多 go func 都没有成功运行,因为在他们成功 add 之前就已经结束 wait 了。
坑点 2:互斥数据的锁
code1
package main
import (
"fmt"
"sync"
"time"
)
var (
ans1 = 0
ans2 = 0
ans3 = 0
wg sync.WaitGroup
)
func TestA(a int) {
//fmt.Println("a:", a)
defer wg.Done()
ans1 = ans1 + a
}
func main() {
for r := 0; r < 1000; r++ {
ans1 = 0
ans2 = 0
ans3 = 0
fmt.Println("第", r, "次测试")
wg.Add(10)
for i := 0; i < 10; i++ {
go TestA(i)
}
wg.Wait()
fmt.Println("ans1:", ans1)
time.Sleep(time.Second)
wg.Add(10)
for i := 0; i < 10; i++ {
go func() {
fmt.Println("i:", i)
ans2 = ans2 + i
wg.Done()
}()
}
wg.Wait()
fmt.Println("ans2:", ans2)
time.Sleep(time.Second)
wg.Add(10)
for i := 0; i < 10; i++ {
ii := i
go func() {
//fmt.Println("ii:", ii)
ans3 = ans3 + ii
wg.Done()
}()
}
wg.Wait()
fmt.Println("ans3:", ans3)
time.Sleep(time.Second)
if ans1 != ans3 {
fmt.Println("ans1 != ans3")
break
}
}
}
需要特别注意三种数据传递方式,这里面第一种和第三种的数据传递是正确的?
不三个都是错误的,因为他们都并发操作了非并发安全的数据
而 ans2 还犯了使用 for 中数据作为传递的错误,这就导致了 ans2 的错误是最离谱的。
code2
package main
import (
"fmt"
"sync"
"time"
)
var (
ans1 = 0
ans2 = 0
ans3 = 0
wg sync.WaitGroup
)
func main() {
for r := 0; r < 1000; r++ {
ans1 = 0
ans2 = 0
ans3 = 0
fmt.Println("第", r, "次测试")
wg.Add(10)
for i := 0; i < 10; i++ {
go TestA(i)
}
wg.Wait()
fmt.Println("ans1:", ans1)
time.Sleep(time.Second)
wg.Add(10)
for i := 0; i < 10; i++ {
ii := i
go func() {
ans3 = ans3 + ii
wg.Done()
}()
}
wg.Wait()
fmt.Println("ans2:", ans3)
time.Sleep(time.Second)
if ans1 != ans3 {
fmt.Println("ans1 != ans2")
break
}
}
}
结论
- ans1 的参数传递正确,但是 ans1 没有进行并发加锁,出现了脏写的情况
- ans2 的参数引用是不可行的,而且也没有进行并发加锁,出现了 i 都是最后的 10,以及脏写的情况
- ans3 的参数传递正确,但是 ans3 没有进行并发加锁,出现了脏写的情况
观察 code1 与 code2 两者的区别便在于两个输出语句,
我们恢复 code1 中 ans1 的输出语句即
func TestA(a int) {
fmt.Println("a:", a)
defer wg.Done()
ans1 = ans1 + a
}
运行十次程序,均出错,出错原因均是 ans3 结果错误。同理,我们恢复 ans3 的输出语句,进行相同的模拟,出错原因均是 ans1 结果错误。
仔细分析后发现是没有对 ans 这些互斥数据进行加锁,导致出现了脏写,而之所以加上输出后没有出错,可能是由于执行时间的差异导致数据成功存储。
加互斥锁之后可以成功通过千次测验。
package main
import (
"fmt"
"sync"
"time"
)
var (
ans1 = 0
ans2 = 0
ans3 = 0
wg sync.WaitGroup
mutex1 = make(chan struct{}, 1)
mutex3 = make(chan struct{}, 1)
)
func TestA(a int) {
defer wg.Done()
//申请锁
mutex1 <- struct{}{}
ans1 = ans1 + a
//释放锁
<-mutex1
}
func main() {
for r := 0; r < 1000; r++ {
ans1 = 0
ans2 = 0
ans3 = 0
fmt.Println("第", r, "次测试")
wg.Add(10)
for i := 0; i < 10; i++ {
go TestA(i)
}
wg.Wait()
fmt.Println("ans1:", ans1)
time.Sleep(time.Second)
//wg.Add(10)
//for i := 0; i < 10; i++ {
// go func() {
// ans2 = ans2 + i
// wg.Done()
// }()
//}
//wg.Wait()
//fmt.Println("ans2:", ans2)
//time.Sleep(time.Second)
wg.Add(10)
for i := 0; i < 10; i++ {
ii := i
go func() {
//申请锁
mutex3 <- struct{}{}
ans3 = ans3 + ii
wg.Done()
//释放锁
<-mutex3
}()
}
wg.Wait()
fmt.Println("ans3:", ans3)
time.Sleep(time.Second)
if ans1 != ans3 {
fmt.Println("ans1 != ans3")
break
}
}
}