使用goroutine和channel可以非常简单的解决一读一写多着多读一写的线程协作问题,而且一个goroutine只负责写,或者读,不可能又写又读。
不过还是会不免要遇到多个goroutine同时要读写的问题。
常举的一个例子就是银行账户的存取问题,在这个问题模型中,多个goroutine会同时出现写操作,最常见的bug就是用户AB同时进入读取块,都发现还剩下100元,此时如果他们同时取钱,账户就会变成-100元
一种常见的解决方法就是再开一个线程,只要有多于一个线程同时访问某变量,都可以再开一个线程,然后把访问委托给他,此时访问变量的就只有一个线程了。
使用go的select,就可以非常优雅的解决这个问题
package bank
var deposits = make(chan int) // 存取钱的请求队列
var balances = make(chan int) //读取钱的请求队列
//每次调用方法时,只是将请求放在队列中
func Deposit(amount int) { deposits <‐ amount }
func Balance() int { return <‐balances }
func teller() {
var balance int // 共享变量被限定在单个线程中,外部无法修改
for {
//使用select来处理队列中的消息
select {
//
case amount := <‐deposits:
balance += amount
case balances <‐ balance:
}
}
}
func init() {
go teller() // 开启中间线程来处理人物
}
还有一个解决办法就是使用二元信号量来保证同一时间只有goroutine在访问共享变量,和上面的不同点主要是不用新开线程,而是在访问时将另一条线程挂起。
import "sync"
var (
mu sync.Mutex // guards balance
balance int
)
func Deposit(amount int) {
//加锁
mu.Lock()
balance = balance + amount
//解锁
mu.Unlock()
}
func Balance() int {
//加锁
mu.Lock()
b := balance
//解锁
mu.Unlock()
return b
}
欢迎关注我的github
https://github.com/luckyCatMiao