企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
## 9.3\. 同步(Synchronization) ### 9.3.1\. 初始化 程序的初始化在一个独立的goroutine中执行。在初始化过程中创建的goroutine将在 第一个用于初始化goroutine执行完成后启动。 如果包p导入了包q,包q的init 初始化函数将在包p的初始化之前执行。 程序的入口函数 main.main 则是在所有的 init 函数执行完成 之后启动。 在任意init函数中新创建的goroutines,将在所有的init 函数完成后执行。 ### 9.3.2\. Goroutine的创建 用于启动goroutine的go语句在goroutine之前运行。 例如,下面的程序: ``` var a string; func f() { print(a); } func hello() { a = "hello, world"; go f(); } ``` 调用hello函数,会在某个时刻打印“hello, world”(有可能是在hello函数返回之后)。 ### 9.3.3\. Channel communication 管道通信 用管道通信是两个goroutines之间同步的主要方法。在管道上执行的发送操作会关联到该管道的 接收操作,这通常对应goroutines。 管道上的发送操作发生在管道的接收完成之前(happens before)。 例如这个程序: ``` var c = make(chan int, 10) var a string func f() { a = "hello, world"; c <- 0; } func main() { go f(); <-c; print(a); } ``` 可以确保会输出"hello, world"。因为,a的赋值发生在向管道 c发送数据之前,而管道的发送操作在管道接收完成之前发生。 因此,在print 的时候,a已经被赋值。 从一个unbuffered管道接收数据在向管道发送数据完成之前发送。 下面的是示例程序: ``` var c = make(chan int) var a string func f() { a = "hello, world"; <-c; } func main() { go f(); c <- 0; print(a); } ``` 同样可以确保输出“hello, world”。因为,a的赋值在从管道接收数据 前发生,而从管道接收数据操作在向unbuffered 管道发送完成之前发生。所以,在print 的时候,a已经被赋值。 如果用的是缓冲管道(如 c = make(chan int, 1) ),将不能保证输出 “hello, world”结果(可能会是空字符串, 但肯定不会是他未知的字符串, 或导致程序崩溃)。 ### 9.3.4\. 锁 包sync实现了两种类型的锁: sync.Mutex 和 sync.RWMutex。 对于任意 sync.Mutex 或 sync.RWMutex 变量l。 如果 n &lt; m ,那么第n次 l.Unlock() 调用在第 m次 l.Lock() 调用返回前发生。 例如程序: ``` var l sync.Mutex var a string func f() { a = "hello, world"; l.Unlock(); } func main() { l.Lock(); go f(); l.Lock(); print(a); } ``` 可以确保输出“hello, world”结果。因为,第一次 l.Unlock() 调用(在f函数中)在第二次 l.Lock() 调用 (在main 函数中)返回之前发生,也就是在 print 函数调用之前发生。 For any call to l.RLock on a sync.RWMutex variable l, there is an n such that the l.RLock happens (returns) after the n'th call to l.Unlock and the matching l.RUnlock happens before the n+1'th call to l.Lock. ### 9.3.5\. Once 包once提供了一个在多个goroutines中进行初始化的方法。多个goroutines可以 通过 once.Do(f) 方式调用f函数。 但是,f函数 只会被执行一次,其他的调用将被阻塞直到唯一执行的f()返回。 once.Do(f) 中唯一执行的f()发生在所有的 once.Do(f) 返回之前。 有代码: ``` var a string func setup() { a = "hello, world"; } func doprint() { once.Do(setup); print(a); } func twoprint() { go doprint(); go doprint(); } ``` 调用twoprint会输出“hello, world”两次。第一次twoprint 函数会运行setup唯一一次。