参考《gopl》
pdf: https://books.studygolang.com/download/gopl-zh.pdf
github code:https://github.com/adonovan/gopl.io/
仅做个人笔记,浏览请看原书
三种方式避免资源竞争
no.1
var icons = map[string]image.Image{
"spades.png": loadIcon("spades.png"),
"hearts.png": loadIcon("hearts.png"),
"diamonds.png": loadIcon("diamonds.png"),
"clubs.png": loadIcon("clubs.png"),
}
// Concurrency-safe.
func Icon(name string) image.Image { return icons[name] }
上面的例子里icons变量在包初始化阶段就已经被赋值了,包的初始化是在程序main函数开始 执行之前就完成了的。只要初始化完成了,icons就再也不会修改的或者不变量是本来就并发 安全的,这种变量不需要进行同步。不过显然我们没法用这种方法,因为update操作是必要 的操作,尤其对于银行账户来说。
no.2
避免从多个goroutine访问变量。这也是前一章中大多数程序 所采用的方法。例如前面的并发web爬虫(§8.6)的main goroutine是唯一一个能够访问seen map的goroutine,而聊天服务器(§8.10)中的broadcaster goroutine是唯一一个能够访问clients map的goroutine。这些变量都被限定在了一个单独的goroutine中。由于其它的goroutine不能够直接访问变量,它们只能使用一个channel来发送给指定的 goroutine请求来查询更新变量。这也就是Go的口头禅“不要使用共享数据来通信;使用通信来 共享数据”。一个提供对一个指定的变量通过channel来请求的goroutine叫做这个变量的监控 (monitor)goroutine。改写后的银行的例子:
// Package bank provides a concurrency-safe bank with one account.
package bank
var deposits = make(chan int) // send amount to deposit 存款
var balances = make(chan int) // receive balance
func Deposit(amount int) { deposits <- amount }
func Balance() int { return <-balances }
func teller() {
var balance int // balance is confined to teller goroutine
for {
select {
case amount := <-deposits:
balance += amount
case balances <- balance:
}
}
}
func init() {
go teller() // start the monitor goroutine
}
no.3
允许很多goroutine去访问变量,但是在同一个时刻最多只有一 个goroutine在访问。这种方式被称为“互斥”.
即使当一个变量无法在其整个生命周期内被绑定到一个独立的goroutine,绑定依然是并发问 题的一个解决方案。例如在一条流水线上的goroutine之间共享变量是很普遍的行为,在这两 者间会通过channel来传输地址信息。如果流水线的每一个阶段都能够避免在将变量传送到下 一阶段时再去访问它,那么对这个变量的所有访问就是线性的。其效果是变量会被绑定到流 水线的一个阶段,传送完之后被绑定到下一个,以此类推。这种规则有时被称为串行绑定。 下面的例子中,Cakes会被严格地顺序访问,先是baker gorouine,然后是icer gorouine:
type Cake struct{ state string }
func baker(cooked chan<- *Cake) {
for {
cake := new(Cake)
cake.state = "cooked"
cooked <- cake // baker never touches this cake again
}
}
func icer(iced chan<- *Cake, cooked <-chan *Cake) {
for cake := range cooked {
cake.state = "iced"
iced <- cake // icer never touches this cake again
}
}
sync.Mutex互斥锁
var (
sema = make(chan struct{}, 1) // a binary semaphore guarding balance
balance int
)
func Deposit(amount int) {
sema <- struct{}{} // acquire token
balance = balance + amount
<-sema // release token
}
func Balance() int {
sema <- struct{}{} // acquire token
b := balance
<-sema // release token
return b
}
缓冲区为1的channel可以直接使用互斥锁来代替:
import "sync"
var (
mu sync.Mutex // guards balance
balance int
//惯例来说,被mutex所保护的变量是在mutex变量声明之后立刻声明的。
//如果你的做法和惯例不符,确保在文档里对你的做法进行说明。
)
func Deposit(amount int) {
mu.Lock()
balance = balance + amount
mu.Unlock()
}
func Balance() int {
mu.Lock()
b := balance
mu.Unlock()
return b
}
sync.RWMutex读写锁
RWMutex需要更 复杂的内部记录,所以会让它比一般的无竞争锁的mutex慢一些。
golang中没有可重入锁
sync.Once初始化
一次性的初始化需要一个互斥量mutex和一个boolean变量来记录初始化是不是已经完成了; 互斥量用来保护boolean变量和客户端数据结构。Do这个唯一的方法需要接收初始化函数作为 其参数
var loadIconsOnce sync.Once
var icons map[string]image.Image
// Concurrency-safe.
func Icon(name string) image.Image {
loadIconsOnce.Do(loadIcons)
return icons[name]
}
每一次对Do(loadIcons)的调用都会锁定mutex,并会检查boolean变量。在第一次调用时,变 量的值是false,Do会调用loadIcons并会将boolean设置为true。随后的调用什么都不会做, 但是mutex同步会保证loadIcons对内存(这里其实就是指icons变量啦)产生的效果能够对所有 gopl sync.Once初始化 355 goroutine可见。用这种方式来使用sync.Once的话,我们能够避免在变量被构建完成之前和其 它goroutine共享该变量。