gopl-9. 基于共享变量的并发

 参考《gopl》

pdf: https://books.studygolang.com/download/gopl-zh.pdf

官网:http://www.gopl.io/

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共享该变量。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值