Go语言并发–传统锁与channel的选择
虽然go语言同时支持CSP原语和内存访问同步。但是更推崇使用channel编写高水平并发。
“不要通过共享内存进行通信,相反,通过通信来共享内存。”
Go语言在sync包中提供了传统的锁机制,大多数的锁问题都可以通过channel或者传统的锁来解决,所以,我该用哪个?
- 你想要转让数据的所有权么?
如果你有一块产生的计算结果并想共享这个结果给其它代码块的代码,你所做的就是传递了数据的所有权。
数据拥有所有权者,并发程序安全就是保证同时只有一个并发上下文拥有数据的所有权。channel通过将这个意图编进channel类型本身来帮助我们来表达这个意图。
这么做的一个很大的好处就是可以创建一个带缓存的channel来实现一个低成本的内存中的队列来解耦你的生产者和消费者;另一个好处就是通过使用channel确保你的并发代码可以与其他并发代码进行组合
2.你是否在试图保护某个结构的内部状态?
这个时候内存访问同步原语的一个很好的选择,也是一个你不应该使用channel的很好的示例。通过使用内存访问的同步原语,可以为你的调用者隐藏关于重要代码块的实现细节。下面是一个线程安全类型的小例子,且不会给调用者带来复杂性:
type Counter struct {
mu sync.Mutex
value int
}
func (c Counter) Increment() {
c.mu.Lock()
defer c.mu.Unlock()
c.value++
}
我们这里所做的就是定义了Counter类型的原子性范围(应尽量缩小原子范围),调用Increment可被认为是原子的。
记住这里的关键词是“内部的”。如果你发现自己正在将锁暴露在一个类型之外,这个时候你应该注意了。试图将你的锁放在一个小的字典范围内。
3.你是否试图协调多个逻辑片段?
channel本质上比内存访问同步原语更具有可组合性。将锁分散在整个对象图中听起来像是噩梦一场,但是,将channel编写的随处可见是被鼓励的以及期待的!
我们可以组合channel,但是我们不能轻易的组合锁或者有返回值的方法。
4.这是一个对性能要求很高的临界区吗?
这并不是意味着“我想让我的程序拥有高性能,因此,我应该只是用mutex”。,当然,如果你的程序中的某部分,事实证明是一个主要的性能瓶颈,比程序的其它部分慢几个数量级,使用内存访问同步原语可能会帮助这个重要的部分在负载下执行,这是因为channel使用内存访问同步来操作,因此它们只能更慢,然而,我们在考虑这一点前,性能至关重要的程序部分可能暗示着需要重新规划我买的程序