提高网站收录率非常有效的方法
1.序言
当然在 go 中 ,并发编程并发编程特别很是完成复杂, 只需求独霸 go func() 就可以启动一个 goroutine 往做一些工作,数据可是竞争恰是因为这类复杂我们要特别很是寄看 ,不然很随便展示一些莫明其妙的并发编程 bug 或是你的处事因为不驰名的启事就重启了。 而最罕有的完成bug是关于线程安然方面的问题,比如对不合个map举办写独霸。数据
2.数据竞争
线程安然可否有甚么编制检测到呢 ?竞争
谜底就是 data race tag ,go 官方早在 1.1 版本就引进了数据竞争的并发编程检测对象 ,我们只需求在奉行测试或是完成编译的时辰加上 -race 的 flag 便可以开启数据竞争的检测
独霸编制以下
go test -race main.gogo build -race
不建议在花费气候 build 的时辰开启数据竞争检测,因为这会带来必定的数据功用丧损掉落踪落(一样往常内存5-10倍 ,奉行时分2-20倍)
,竞争当然 必须求 debug 的并发编程时辰除外
。
建议在奉行单位测试时不竭开启数据竞争的完成检测
2.1 示例一
奉行以下代码 ,搜检每次奉行的数据下场可否一样
2.1.1 测试
代码
package main import ( "fmt" "sync") var wg sync.WaitGroupvar counter int func main() { // 多跑几归来算作果 for i := 0; i < 100000; i++ { run() } fmt.Printf("Final Counter: %d\n", counter)} func run() { // 开启两个 协程,独霸 for i := 1; i <= 2; i++ { wg.Add(1) go routine(i) } wg.Wait()} func routine(id int) { for i := 0; i < 2; i++ { value := counter value++ counter = value } wg.Done()}奉行三次搜检下场,分袂是
Final Counter: 399950
Final Counter: 399989
Final Counter: 400000
启事分化 :每次奉行的时辰 ,都独霸 go routine(i) 启动了两个 goroutine ,可是并没有独霸它的奉行按序 ,真实不克不及知足按序齐截性内存模型 。
当然因为各类不竭定性 ,全数必定不止这两种气候,
2.1.2 data race 检测
上面问题标涌如今上线后假定展示bug会特别很是难定位,因为不知事幻想下场是何处展示了问题 ,所以我们就要在测试阶段就连络 data race 对象延迟创作创造问题 。
独霸
go run -race ./main.go
输进: 运转下场创作创造输进记实太长,调试的时辰真实不直不美不雅不雅不雅不雅不雅不雅,下场以下
main.main()
D:/gopath/src/Go_base/daily_test/data_race/demo.go:14 +0x44
==================
Final Counter: 399987
Found 1 data race(s)
exit status 66
2.1.3 data race 设置设备放置
在官方的文档傍边 ,可以经由过程设置 GORACE 气候变量,来独霸 data race 的行动 , 格式以下:
GORACE="option1=val1 option2=val2"
可选设置设备放置见下表

设置设备放置
GORACE="halt_on_error=1 strip_path_prefix=/mnt/d/gopath/src/Go_base/daily_test/data_race/01_data_race" go run -race ./demo.go
输进 :
==================
WARNING: DATA RACE
Read at 0x00000064d9c0 by goroutine 8:
main.routine()
/mnt/d/gopath/src/Go_base/daily_test/data_race/demo.go:31 +0x47
Previous write at 0x00000064d9c0 by goroutine 7:
main.routine()
/mnt/d/gopath/src/Go_base/daily_test/data_race/demo.go:33 +0x64
Goroutine 8 (running) created at:
main.run()
/mnt/d/gopath/src/Go_base/daily_test/data_race/demo.go:24 +0x75
main.main()
/mnt/d/gopath/src/Go_base/daily_test/data_race/demo.go:14 +0x3c
Goroutine 7 (finished) created at:
main.run()
/mnt/d/gopath/src/Go_base/daily_test/data_race/demo.go:24 +0x75
main.main()
/mnt/d/gopath/src/Go_base/daily_test/data_race/demo.go:14 +0x3c
==================
exit status 66
声明:下场陈述可以看出 31 行这个中心有一个 goroutine 在读取数据
,可是呢,在 33 行这个中心又有一个 goroutine 在写进 ,所以产生发火了数据竞争。
然后上面分袂声明这两个 goroutine 是甚么时辰成立的
,以前后可否在运转傍边。
2.2 轮回中独霸goroutine援引且则变量
代码以下 :
func main() { var wg sync.WaitGroup wg.Add(5) for i := 0; i < 5; i++ { go func() { fmt.Println(i) wg.Done() }() } wg.Wait()}输进:罕有的谜底就是会输进 5 个 5
,因为在 for 轮回的 i++ 会奉行的快一些,所以在末尾打印的下场都是 5
这个谜底不克不及说舛错,因为真的奉行的话大年夜大年夜体率也是这个下场
,可是不全 。因为这里本质上是罕有据竞争 ,在新启动的 goroutine 傍边读取 i 的值
,在 main 中写进,招致展示了 data race,这个下场理应是不成预知的,因为我们不克不及假定 goroutine 中 print 就必定比外不雅的 i++ 慢 ,习惯性的做这类假定在并发编程中是很有大年夜大年夜约会出问题标
切确示例 :将 i 作为参数传进便可,多么每个 goroutine 拿到的都是拷贝后的数据
func main() { var wg sync.WaitGroup wg.Add(5) for i := 0; i < 5; i++ { go func(i int) { fmt.Println(i) wg.Done() }(i) } wg.Wait()}2.3 激起变量共享
代码
package main import "os" func main() { ParallelWrite([]byte("***"))} // ParallelWrite writes data to file1 and file2, returns the errors.func ParallelWrite(data []byte) chan error { res := make(chan error, 2) // 成立/写进第一个文件 f1, err := os.Create("/tmp/file1") if err != nil { res <- err } else { go func() { // 上面的这个函数在奉行时,是独霸err举办剖断,可是err的变量是个共享的变量 _, err = f1.Write(data) res <- err f1.Close() }() } // 成立写进第二个文件n f2, err := os.Create("/tmp/file2") if err != nil { res <- err } else { go func() { _, err = f2.Write(data) res <- err f2.Close() }() } return res}分化 : 独霸 go run -race main.go 奉行 ,可以创作创造这里报错的中心是 ,21 行和 28 行,有 data race,这里主假定因为共享了 err 这个变量
root@failymao:/mnt/d/gopath/src/Go_base/daily_test/data_race# go run -race demo2.go==================WARNING: DATA RACEWrite at 0x00c0001121a0 by main goroutine: main.ParallelWrite() /mnt/d/gopath/src/Go_base/daily_test/data_race/demo2.go:28 +0x1dd main.main() /mnt/d/gopath/src/Go_base/daily_test/data_race/demo2.go:6 +0x84 Previous write at 0x00c0001121a0 by goroutine 7: main.ParallelWrite.func1() /mnt/d/gopath/src/Go_base/daily_test/data_race/demo2.go:21 +0x94 Goroutine 7 (finished) created at: main.ParallelWrite() /mnt/d/gopath/src/Go_base/daily_test/data_race/demo2.go:19 +0x336 main.main() /mnt/d/gopath/src/Go_base/daily_test/data_race/demo2.go:6 +0x84==================Found 1 data race(s)exit status 66
改削: 在两个goroutine中独霸新的且则变量
_, err := f1.Write(data)..._, err := f2.Write(data)...
2.4 不受呵护的全局变量
所谓全局变量是指 ,定义在多个函数的感染域以外 ,可以被多个函数或编制举办调用,经常独霸的如 map数据圭表类型
// 定义一个全局变量 map数据圭表类型var service = map[string]string{ } // RegisterService RegisterService// 用于写进或更新key-valuefunc RegisterService(name, addr string) { service[name] = addr} // LookupService LookupService// 用于查询某个key-valuefunc LookupService(name string) string { return service[name]}要写出可测性斗劲高的代码就要罕用或是尽大年夜大年夜约阻拦用全局变量,独霸 map 作为全局变量斗劲罕有的一种气候就是设置设备放信赖息。关于全局变量的话一样往常的做法就是加锁,或也可独霸 sync.Ma
var (service map[string]stringserviceMu sync.Mutex) func RegisterService(name, addr string) { serviceMu.Lock() defer serviceMu.Unlock() service[name] = addr} func LookupService(name string) string { serviceMu.Lock() defer serviceMu.Unlock() return service[name]}2.5 未受呵护的成员变量
一样往常讲成员变量 指的是数据圭表类型为筹划体的某个字段。 以下一段代码
type Watchdog struct{ last int64} func (w *Watchdog) KeepAlive() { // 第一次举办赋值独霸 w.last = time.Now().UnixNano() } func (w *Watchdog) Start() { go func() { for { time.Sleep(time.Second) // 这里在举办剖断的时辰,很大年夜大年夜约w.last更新正在举办 if w.last < time.Now().Add(-10*time.Second).UnixNano() { fmt.Println("No keepalives for 10 seconds. Dying.") os.Exit(1) } } }()}独霸原子独霸atomiic
type Watchdog struct{ last int64 } func (w *Watchdog) KeepAlive() { // 改削或更新 atomic.StoreInt64(&w.last, time.Now().UnixNano())} func (w *Watchdog) Start() { go func() { for { time.Sleep(time.Second) // 读取 if atomic.LoadInt64(&w.last) < time.Now().Add(-10*time.Second).UnixNano() { fmt.Println("No keepalives for 10 seconds. Dying.") os.Exit(1) } } }()}2.6 接口中存在的数据竞争
一个很滑稽的例子 Ice cream makers and data races
package main import "fmt" type IceCreamMaker inte***ce { // Great a customer. Hello()} type Ben struct { name string} func (b *Ben) Hello() { fmt.Printf("Ben says, \"Hello my name is %s\"\n", b.name)} type Jerry struct { name string} func (j *Jerry) Hello() { fmt.Printf("Jerry says, \"Hello my name is %s\"\n", j.name)} func main() { var ben = &Ben{ name: "Ben"} var jerry = &Jerry{ "Jerry"} var maker IceCreamMaker = ben var loop0, loop1 func() loop0 = func() { maker = ben go loop1() } loop1 = func() { maker = jerry go loop0() } go loop0() for { maker.Hello() }}这个例子滑稽的点在于,末尾输进的下场会有这类例子
Ben says, "Hello my name is Jerry"
Ben says, "Hello my name is Jerry"
这是因为我们在maker = jerry这类赋值独霸的时辰真实不是原子的 ,在上一篇文章中我们讲到过,只需对 single machine word 举办赋值的时辰才是原子的 ,当然这个看上往只需一行,可是 inte***ce 在 go 中理论上是一个筹划体 ,它包含了 type 和 data 两个部分 ,所以它的复制也不是原子的,会展示问题
type inte***ce struct { Type uintptr // points to the type of the inte***ce implementation Data uintptr // holds the data for the inte***ce's receiver}这个案例滑稽的点还在于 ,这个案例的两个筹划体的内存筹划千篇一概所以展示偏向也不会 panic 介入 ,假定在里面再介入一个 string 的字段 ,往读取就会招致 panic ,可是这也正好声明这个案例很恐怖,这类偏向在线上真实太难创作创造了 ,并且很有大年夜大年夜约会很致命。
3. 总结
独霸 go build -race main.go和go test -race ./ 可以测试法度圭表类型代码中可否存在数据竞争问题
- 善用 data race 这个对象辅佐我们延迟创作创造并发偏向
- 不要对不决义的行动做任何假定,当然有时光我们写的只是一行代码,可是 go 编译器大年夜大年夜约后背做了良多工作 ,真实不是说一行写完就必定是原子的
- 即就是原子的展示了 data race 也不克不及担保安然,因为我们另有可见性的问题,上篇我们讲到了现代的 cpu 根本上都邑有一些缓存的独霸 。
- 全数展示了 data race 的中心都需求举办措置
4 参考
https://lailin.xyz/post/go-training-week3-data-race.html#类型案例
https://dave.cheney.net/2014/06/27/ice-cream-makers-and-data-races
http://blog.golang.org/race-detector
https://golang.org/doc/articles/race_detector.html
https://dave.cheney.net/2018/01/06/if-aligned-memory-writes-are-atomic-why-do-we-need-the-sync-atomic-package
到此这篇关于Go并发编程完成数据竞争的文章就引见到这了,更多相干Go 数据竞争内容请搜刮完竣下载之前的文章或延续不雅不雅不雅不雅鉴赏上面的相干文章希看大年夜师往后多多支撑完竣下载 !
