通过设计俩函数,对全局变量自增和自减10000次来重现并发不加锁带来的问题,代码如下,Test
package main
import (
"fmt"
"time"
)
var i =1
func Test()int{
go add()
go dec()
// sleep 1s,阻塞主协程保证俩协程有足够的执行时间
time.Sleep(1*time.Second)
// 正常情况下,i自增和自减10000次后,最后i的值应该不会发生改变,这里预期应该是1,但结果并不是
fmt.Println(i)
return i
}
//对全局变量i自减10000次
func dec() {
for j:=0;j<10000;j++{
i--
}
}
//对全局变量i自增10000次
func add(){
for j:=0;j<10000;j++{
i++
}
}
下面benchmark 10次看看效果,测试用例如下:执行go test -v -bench=".*"
package main
import (
"testing"
)
func Benchmark_TimeConsumingFunction(b *testing.B) {
b.StopTimer() //调用该函数停止压力测试的时间计数
//做一些初始化的工作,例如读取文件数据,数据库连接之类的,
//这样这些时间不影响我们测试函数本身的性能
b.StartTimer() //重新开始时间
b.N=10 //自定义执行10次
// 执行10次Test(),即重复10次自增自减
for i := 0; i < b.N; i++ {
ret:=Test()
if ret!=1{
b.Errorf("fail")
}
}
}
10次跑的结果如下,为什么会出现这种情况?
因为++,--操作虽然只有一句,但对计算机来说,并不是原子操作,编译的时候代码会变成3步,register=i, register=register+1,
i=register,如果有多个进程或者协程,都执行i++,或者i--操作,在这三步里就会穿插执行这些语句,就会造成不可预知的结果,因此为了避免这种情况,在并发读写时,需要加锁,这样,就可以保证三步执行期间不会引入新的语句
1
1
1
-4136
-4201
-6522
-6855
-4380
-4380
-1129
--- FAIL: Benchmark_TimeConsumingFunction
pcb_test.go:20: fail
pcb_test.go:20: fail
pcb_test.go:20: fail
pcb_test.go:20: fail
pcb_test.go:20: fail
pcb_test.go:20: fail
pcb_test.go:20: fail
FAIL
exit status 1
FAIL github.com/Altruiste1/ctest 10.323s