Go Data race and Vector clock

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

Go Data race and Vector clock

Data race 数据竞态(争)

data race是指多个运行分支线程同时访问某共享资源并且有写操作的情况。此时该共享资源因同时发生读写其数据状态不确定。编码时应杜绝数据竞态问题的产生。

如下是一个简单的例子global 全局变量初始状态为0我们启动两个协程同时对global进行自增1的操作各执行1万次。我们的假想是该代码执行完打印global时值为20000。但并非如此可能偶尔是20000。当前运行结果16449。

package main

import (
	"fmt"
	"time"
)

var global = 0

func main() {
	go func() {
		for i := 0; i < 10000; i++ {
			global++
		}
	}()

	for i := 0; i < 10000; i++ {
		global++
	}
	time.Sleep(time.Second)
	fmt.Println(global)
}

造成这种问题的根源是对global的自增操作不具有原子性。并且有多个协程对没有锁保护的资源进行访问。

使用 -race 子命令检测数据竞争

这里使用go run -race main.go来检测main.go文件在执行过程中是否有竞态问题。-race 不仅可以用在 run 子命令后还可用在 build test 之后。

...\main>go run -race main.go
==================
WARNING: DATA RACE                                          
Read at 0x000000638610 by goroutine 7:                      
  main.main.func1()                                         
      .../main/main.go:13 +0x32
                                                            
Previous write at 0x000000638610 by main goroutine:         
  main.main()                                               
      .../main/main.go:18 +0x5d
                                                            
Goroutine 7 (running) created at:                           
  main.main()                                               
      .../main/main.go:11 +0x30
==================                                          
20000
Found 1 data race(s)
exit status 66

可以看出-race子命令报告了代码Found 1 data race(s)并且标记了代码发生数竞争的位置。

data race 解决办法

当你的代码存在数据竞争时一定要解决它。解决办法有几种1. 对共享资源加锁2.操作共享资源时使用原子操作(适用范围比较小)3.使用通道。其目的都是保证读写不在同一时刻发生产生互斥的效应。

加锁

package main

import (
	"fmt"
	"sync"
	"time"
)

var global = 0
var globalMu = sync.Mutex{}

func main() {
	go func() {
		for i := 0; i < 10000; i++ {
			globalMu.Lock()
			global++
			globalMu.Unlock()
		}
	}()

	for i := 0; i < 10000; i++ {
		globalMu.Lock()
		global++
		globalMu.Unlock()
	}
	time.Sleep(time.Second)
	fmt.Println(global)
}

再次使用go run -race main.go检测运行中代码是否存在竞态问题

...\main>go run -race main.go
20000

可以看出没有竞态问题的发生了。

原子操作

此时将global修改为int64类型。

package main

import (
	"fmt"
	"sync/atomic"
	"time"
)

var global int64 = 0

func main() {
	go func() {
		for i := 0; i < 10000; i++ {
			atomic.AddInt64(&global, 1)
		}
	}()

	for i := 0; i < 10000; i++ {
		atomic.AddInt64(&global, 1)
	}
	time.Sleep(time.Second)
	fmt.Println(global)
}

通道

package main

import (
	"fmt"
	"time"
)

var global int64 = 0

func main() {
	ch := make(chan struct{}, 1)
	go func() {

		for i := 0; i < 10000; i++ {
			ch <- struct{}{}
			global++
			<-ch
		}
	}()

	for i := 0; i < 10000; i++ {
		ch <- struct{}{}
		global++
		<-ch
	}

	time.Sleep(time.Second)
	fmt.Println(global)
}

以上办法都是利用同步机制来防止同一时刻读写数据的问题的发生。使读写操作满足happened-before原则。

Vector clock

A vector clock is a data structure used for determining the partial ordering of events in a distributed system and detecting causality violations.
向量时钟是一种数据结构用于确定分布式系统中事件的部分排序并检测因果关系违规。
Just as in Lamport timestamps, inter-process messages contain the state of the sending process’s logical clock.
就像在Lamport时间戳一样过程间消息包含发送过程的逻辑时钟的状态。
A vector clock of a system of N processes is an array/vector of N logical clocks, one clock per process; a local “largest possible values” copy of the global clock-array is kept in each process.
n个过程系统的向量时钟是n个逻辑时钟的数组/向量每个过程一个时钟在每个过程中保存全局时钟阵列的本地“最大值”副本。

以上文字来自维基英文百科。

Go race 检测原理

Go 语言内置的race检测工具为Google的Threadsanitizer其内部原理即为Vector clock矢量时钟。Go语言在其源码中插入了大量的race相关代码这些探针在需要race检测时被触发同时编译器还会向可能发生data race的用户代码中插入探针。这些探针作为维护的矢量时钟可以感知到race的发生。由于插入探针所以需要race检测的代码运行效率会更低且内内存占用会更大。

Reference
https://en.wikipedia.org/wiki/Vector_clock
《Go 语言底层原理剖析》

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: go