在并发编程中,对共享资源的并发访问会导致各种问题,如数据竞赛。为了消除意外行为,对共享资源的并发访问必须受到保护。这个被保护的部分被称为关键部分。关键部分绝不能被一个以上的进程同时执行。
在这个例子中,我们要确保一个银行账户状态的完整性不被破坏。我们将通过使用两种通用的同步机制来实现这一点,这两种机制是semaphore和mutex。这两种机制都是用来保护关键部分的。它们实现了互斥,限制了对多种资源的访问,解决了读者和写者的问题,等等。
-
突变器:Mutex通过允许单个线程独占访问(相互排斥)来保护关键部分。
-
傀儡(Semaphore):semaphore(一个变量或一个抽象数据类型)通过允许N个线程独占访问(互斥)来保护一个关键部分。
鉴于信号灯可以允许对共享资源进行多次访问,我们将把它限制为1。这被称为二进制信号灯,与使用容量为1的缓冲通道的情况相同。
银行骨架
这是我们的银行账户的基本框架。当我们使用不同的例子时,我们会更新它。
package bank
type Account struct {
fund float64
}
func (a *Account) Balance() float64 {
return a.fund
}
func (a *Account) Credit(amount float64) {
a.fund += amount
}
func (a *Account) Withdraw(amount float64) {
a.fund -= amount
}
这是我们用来运行测试的主要代码,它将保持原样。
package main
import (
"time"
"internal/bank"
)
func main() {
account := &bank.Account{}
account.Balance("-")
for k, v := range map[int]string{10: "A", 20: "B", 30: "C"} {
go account.Balance(v)
go account.Credit(float64(k), v)
go account.Withdraw(float64(k), v)
}
time.Sleep(1 * time.Second)
account.Balance("+")
}
一次一个读和一个写
确保每次只有一个goroutine访问资金。每个goroutine都有对资金的独占访问权,所以如果一个正在读取,另一个就会等待,反之亦然。这被认为是一个线程安全操作。
选项1:二进制信号/缓冲通道
我们在这里没有直接实现信号灯,但是如果你想创建和使用信号灯包,请看底部的例子。
package bank
import (
"fmt"
"time"
)
var semaphore = make(chan struct{}, 1)
type Account struct {
fund float64
}
func (a *Account) Balance(who string) float64 {
semaphore <- struct{}{}
fmt.Println(who, "Bal", a.fund, "@", time.Now().UTC())
<-semaphore
return a.fund
}
func (a *Account) Credit(amount float64, who string) {
semaphore <- struct{}{}
fmt.Println(who, "Cre", amount, "@", time.Now().UTC())
a.fund += amount
<-semaphore
}
func (a *Account) Withdraw(amount float64, who string) {
semaphore <- struct{}{}
fmt.Println(who, "Wit", amount, "@", time.Now().UTC())
a.fund -= amount
<-semaphore
}
- Bal 0 @ 2020-04-13 14:01:33.650511 +0000 UTC
A Bal 0 @ 2020-04-13 14:01:33.650590 +0000 UTC
B Bal 0 @ 2020-04-13 14:01:33.650662 +0000 UTC
A Cre 10 @ 2020-04-13 14:01:33.650686 +0000 UTC +10
A Wit 10 @ 2020-04-13 14:01:33.650717 +0000 UTC 0
B Cre 20 @ 2020-04-13 14:01:33.650741 +0000 UTC +20
B Wit 20 @ 2020-04-13 14:01:33.650767 +0000 UTC 0
C Bal 0 @ 2020-04-13 14:01:33.650781 +0000 UTC
C Cre 30 @ 2020-04-13 14:01:33.650791 +0000 UTC +30
C Wit 30 @ 2020-04-13 14:01:33.650799 +0000 UTC 0
+ Bal 0 @ 2020-04-13 14:01:34.654512 +0000 UTC
- Bal 0 @ 2020-04-13 14:02:19.709515 +0000 UTC
B Bal 0 @ 2020-04-13 14:02:19.709617 +0000 UTC
C Cre 30 @ 2020-04-13 14:02:19.709702 +0000 UTC +30
B Cre 20 @ 2020-04-13 14:02:19.709728 +0000 UTC +50
B Wit 20 @ 2020-04-13 14:02:19.709746 +0000 UTC +30
C Bal 30 @ 2020-04-13 14:02:19.709786 +0000 UTC
A Bal 30 @ 2020-04-13 14:02:19.709801 +0000 UTC
C Wit 30 @ 2020-04-13 14:02:19.709815 +0000 UTC 0
A Cre 10 @ 2020-04-13 14:02:19.709833 +0000 UTC +10
A Wit 10 @ 2020-04-13 14:02:19.709847 +0000 UTC 0
+ Bal 0 @ 2020-04-13 14:02:20.711135 +0000 UTC
选项2:同步.Mutex
package bank
import (
"fmt"
"sync"
"time"
)
type Account struct {
fund float64
mux sync.Mutex
}
func (a *Account) Balance(who string) float64 {
a.mux.Lock()
defer a.mux.Unlock()
fmt.Println(who, "Bal", a.fund, "@", time.Now().UTC())
return a.fund
}
func (a *Account) Credit(amount float64, who string) {
a.mux.Lock()
defer a.mux.Unlock()
fmt.Println(who, "Cre", amount, "@", time.Now().UTC())
a.fund += amount
}
func (a *Account) Withdraw(amount float64, who string) {
a.mux.Lock()
defer a.mux.Unlock()
fmt.Println(who, "Wit", amount, "@", time.Now().UTC())
a.fund -= amount
}
- Bal 0 @ 2020-04-13 13:47:32.016951 +0000 UTC
A Bal 0 @ 2020-04-13 13:47:32.017039 +0000 UTC
B Bal 0 @ 2020-04-13 13:47:32.017177 +0000 UTC
A Cre 10 @ 2020-04-13 13:47:32.017188 +0000 UTC +10
A Wit 10 @ 2020-04-13 13:47:32.017216 +0000 UTC 0
C Bal 0 @ 2020-04-13 13:47:32.017231 +0000 UTC
C Cre 30 @ 2020-04-13 13:47:32.017244 +0000 UTC +30
B Cre 20 @ 2020-04-13 13:47:32.017254 +0000 UTC +50
B Wit 20 @ 2020-04-13 13:47:32.017266 +0000 UTC +30
C Wit 30 @ 2020-04-13 13:47:32.017280 +0000 UTC 0
+ Bal 0 @ 2020-04-13 13:47:33.021482 +0000 UTC
- Bal 0 @ 2020-04-13 13:48:45.885885 +0000 UTC
A Bal 0 @ 2020-04-13 13:48:45.885986 +0000 UTC
B Cre 20 @ 2020-04-13 13:48:45.886052 +0000 UTC +20
A Wit 10 @ 2020-04-13 13:48:45.886085 +0000 UTC +10
B Bal 10 @ 2020-04-13 13:48:45.886114 +0000 UTC
C Bal 10 @ 2020-04-13 13:48:45.886130 +0000 UTC
B Wit 20 @ 2020-04-13 13:48:45.886142 +0000 UTC -10
C Cre 30 @ 2020-04-13 13:48:45.886157 +0000 UTC +20
C Wit 30 @ 2020-04-13 13:48:45.886169 +0000 UTC -10
A Cre 10 @ 2020-04-13 13:48:45.886183 +0000 UTC 0
+ Bal 0 @ 2020-04-13 13:48:46.891075 +0000 UTC
每次有许多读和一个写
允许许多goroutines在同一时间访问资金(一个共享的变量/资源)。然而,只有一个人可以写入,当这种情况发生时,读取也被阻断。如果没有人在写,则允许许多读,因为它被认为是一个有条件的安全操作。同样,只要没有人同时在写!这对高读流量的情况是很好的。
package bank
import "sync"
type Account struct {
fund float64
mux sync.RWMutex
}
func (a *Account) Balance() float64 {
a.mux.RLock()
defer a.mux.RUnlock()
return a.fund
}
func (a *Account) Credit(amount float64) {
a.mux.Lock()
defer a.mux.Unlock()
a.fund += amount
}
func (a *Account) Withdraw(amount float64) {
a.mux.Lock()
defer a.mux.Unlock()
a.fund -= amount
}
- Bal 0 @ 2020-04-13 13:53:44.060959 +0000 UTC
A Bal 0 @ 2020-04-13 13:53:44.061042 +0000 UTC
B Cre 20 @ 2020-04-13 13:53:44.061125 +0000 UTC +20
B Bal 20 @ 2020-04-13 13:53:44.061158 +0000 UTC
C Bal 20 @ 2020-04-13 13:53:44.061161 +0000 UTC
A Cre 10 @ 2020-04-13 13:53:44.061212 +0000 UTC +30
A Wit 10 @ 2020-04-13 13:53:44.061233 +0000 UTC +20
B Wit 20 @ 2020-04-13 13:53:44.061248 +0000 UTC 0
C Cre 30 @ 2020-04-13 13:53:44.061262 +0000 UTC +30
C Wit 30 @ 2020-04-13 13:53:44.061276 +0000 UTC 0
+ Bal 0 @ 2020-04-13 13:53:45.066051 +0000 UTC
- Bal 0 @ 2020-04-13 13:55:31.374249 +0000 UTC
B Bal 0 @ 2020-04-13 13:55:31.374342 +0000 UTC
A Bal 0 @ 2020-04-13 13:55:31.374336 +0000 UTC
A Cre 10 @ 2020-04-13 13:55:31.374470 +0000 UTC +10
C Bal 10 @ 2020-04-13 13:55:31.374482 +0000 UTC
B Cre 20 @ 2020-04-13 13:55:31.374515 +0000 UTC +30
B Wit 20 @ 2020-04-13 13:55:31.374543 +0000 UTC +10
A Wit 10 @ 2020-04-13 13:55:31.374563 +0000 UTC 0
C Cre 30 @ 2020-04-13 13:55:31.374577 +0000 UTC +30
C Wit 30 @ 2020-04-13 13:55:31.374590 +0000 UTC 0
+ Bal 0 @ 2020-04-13 13:55:32.378968 +0000 UTC
拴马桩包
这就是一个信号包的样子。
package semaphore
type Semaphore chan struct {}
// New returns Semaphore channel. Use 1 for "mutual exclusion".
func New(n int) Semaphore {
return make(Semaphore, n)
}
// acquire `n` amount of resource slots in semaphore channel.
func (s Semaphore) Acquire(n int) {
for i := 0; i < n; i++ {
s <- struct{}{}
}
}
// release `n` amount of resource slots in semaphore channel.
func (s Semaphore) Release(n int) {
for i := 0; i < n; i++ {
<- s
}
}
使用方法
如果你愿意,你可以把它和我们的银行例子一起使用,如下所示。
package bank
import (
"internal/semaphore"
)
const slot = 1
type locker struct {
semaphore.Semaphore
}
func newLocker() locker {
return locker{semaphore.New(slot)}
}
func (l locker) lock() {
l.Semaphore.Acquire(slot)
}
func (l locker) release() {
l.Semaphore.Release(slot)
}
package bank
import (
"fmt"
"time"
)
type Account struct {
fund float64
locker locker
}
func New() *Account {
return &Account{
fund: 0,
locker: newLocker(),
}
}
func (a *Account) Balance(who string) float64 {
a.locker.lock()
fmt.Println(who, "Bal", a.fund, "@", time.Now().UTC())
a.locker.release()
return a.fund
}
func (a *Account) Credit(amount float64, who string) {
a.locker.lock()
fmt.Println(who, "Cre", amount, "@", time.Now().UTC())
a.fund += amount
a.locker.release()
}
func (a *Account) Withdraw(amount float64, who string) {
a.locker.lock()
fmt.Println(who, "Wit", amount, "@", time.Now().UTC())
a.fund -= amount
a.locker.release()
}
如果你想单独使用这个包的话,这里有一个简单的例子。在这种情况下,资源槽被灵活地使用,而不是像我们上面的银行例子那样只有一个槽。
package main
import "fmt"
type (
// empty is a 0 byte struct.
empty struct {}
// semaphore is a channel with empty struct.
semaphore chan empty
)
func main() {
// Initialise semaphore channel with 10 resource slots.
s := make(semaphore, 10)
// Acquire 7 resource slots.
s.acquire(1)
s.acquire(2)
s.acquire(4)
fmt.Println("Available resource slots:", len(s))
// Release 2 resource slots.
s.release(2)
fmt.Println("Available resource slots:", len(s))
}
// acquire `n` amount of resource slots in semaphore channel.
func (s semaphore) acquire(n int) {
for i := 0; i < n; i++ {
s <- empty{}
}
}
// release `n` amount of resource slots in semaphore channel.
func (s semaphore) release(n int) {
for i := 0; i < n; i++ {
<- s
}
}