将共享变量的读写放到一个 goroutine 中,其它 goroutine 通过 channel 进行读写操作,这种方式有很多好处。
package bank
var deposits = make(chan int) // send amout 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 只在 teller 中可以访问
for {
select {
case amount := <-deposits:
balance += amount
case balances <- balance:
}
}
}
func init() {
go teller()
}
第二种就是最常见的互斥了,共享变量要互斥访问
可以用个数为 1 的信号量(semaphore)实现互斥
var (
sema = make(chan struct{}, 1)
balance int
)
func Deposit(amout int) {
sema <- struct{}{} // acquire token
balance = balance + amout
<-sema // release token
}
func Balance() int {
sema <- struct{}{} // acquire token
b := balance
<-sema // release token
return b
}
go 内部提供了互斥操作
import "sync"
var (
mu sync.Mutex
balance int
)
func Deposit(amount int) {
mu.Lock()
defer mu.Unlock()
balance = balance + amount
}
func Balance() int {
mu.Lock()
defer mu.Unlock()
return balance
}
为什么 Balance 也需要互斥,多线程同时读取一个变量有问题吗??? Balance 也需要互斥是为了防止在写的过程中进行读取。如果在读的过程中没有线程在写多线程同时读取是没有问题的。所以为了提高读取的并发量可以用读写锁改写
var mu sync.RWMutex
var balance int
func Balance() int {
mu.RLock() // readers lock
defer mu.RUnlock()
return balance
}
Deposit 不变,Balance 只获取 RLock 读锁,只要没有线程在写,多个线程可同时获得 RLock 锁。
Do not communicate by sharing memory. Instead, share memory by communicating.
sync 库和 channel 的选择要看场景,哪种更简单更适合就用哪种。大部分场景可用 channel 实现。
网友评论