相较于Go语言宣扬的“用通讯的方式共享数据”,通过共享数据的方式来传递信息和协调线程运行的做法其实更加主流,比较大多数的现代编程语言,都是用后一种方式作为并发编程的解决方案的。
一旦数据被多个线程共享,那么就很可能会产生争用和冲突的情况。这种情况被称为竞态条件(race condition),这往往会破坏共享数据的一致性。
共享数据的一致性代表着某种约定,即:多个线程对共享数据的操作总是可以达到它们各自预期的效果。
如果这个一致性得不到保证,那么将会影响到一些线程中代码和流程的正确执行,甚至会造成某种不可预知的错误。
同步解决。同步的用途有两个,一个是避免多个线程在同一时刻操作同一个数据块,另一个是协调多个线程,以避免它们在同一时刻执行同一个代码块。
一个线程在想要访问某一个共享资源的时候,需要先申请对该资源的访问权限,并且只有在申请成功之后,访问才能真正开始。
而当线程对共享资源的访问结束时,它还必须归还对资源的访问权限,若要再次访问仍需申请。
在Go语言中,可供选择的同步工具并不少。其中,最重要并且最常用的同步工具当属互斥量(mutual exclusion,简称mutex)。sync包中的Mutex就是与其对应的类型,该类型的值可以被称为互斥量或者互斥锁。
demo:
mu.Lock()
_,err := writer.Writer([]byte(data))
if err != nil {
logrus.Printf("errors:(%s) [%d]",err, id)
}
mu.Unlock()
使用互斥锁是有哪些注意事项?
- 1.不要重复锁定互斥锁;
- 2.不要忘记解锁互斥锁,必要时使用defer语句;
- 3.不要对尚未锁定或者已解锁的互斥锁解锁;
- 4.不要在多个函数之间直接传递互斥锁;
对一个已经锁定的互斥锁进行锁定,是会立即阻塞当前的goroutine的。这个goroutine所执行的流程,会一直停滞在调用该互斥锁的Lock方法的那行代码上。
直到该互斥锁的Unlock的方法被调用,并且这里的锁定操作成功完成,后续的代码才会开始执行。
一旦,你把一个互斥锁同时用在了多个地方,就必然会有更多的goroutine争用这把锁。这不但会让你的程序变慢,还会大大增加死锁(deadlock)的可能性。
Go语言运行时系统是不允许这种情况出现的,只要它发现所有的用户级goroutine都处于等待状态,就会自行抛出一个带有如下信息的panic:
fatal error: all goroutines are asleep - deadlock!
注意:Go语言运行时系统自行抛出的panic都属于致命错误,都是无法被恢复的,调用recover函数对它们起不到任何作用。也就是说,一旦产生死锁,程序必然崩溃。
一旦我们声明了一个sync.Mutex类型的变量,就可以直接使用它了。不过要注意的,该类型是一个结构体类型,属于值类型中的一种。把它传给一个函数、将它从函数中返回、把它赋给其他的变量、让它进入某个通道都会导致它的副本的产生。
网友评论