多读单写。将 lock 分为 read 与 write 两种,让 lock 效能更佳,read 行为不会改变资料,所以 read lock 时可以再同时 read,达到「多读」,而 write 行为会改变资料,所以 write lock 不能再同时 read 与 write 时,达到「单写」
read, write 中在取得 lock 的时候,只要碰到以下两种「冲突 conflict」状态,就会等待此冲突结束,再取得 lock:
而在以下情形,会直接取得 lock,不会等待此状态结束:
延续上篇 Single Threaded Execution Pattern 的情境,
当设计一个点赞系统上线,允许一人有多个赞,此时会有多个 Client 进行点赞,系统需确保点赞数量正确无误,还须确保读写效能不受到太大影响。
有可能会发生,A、B、C、D 同时两人点赞与两人读赞,由於 A 在读取赞数後,A 还没写入 B、C 又进行了读取,随後 A 加一写入後,B、C 现有的资料还是未加一的,B、C 将资料拿去显示,就会是旧的数字,如图:
相关的 code 在Github - go-design-patterns。
实作有问题的系统如下,A、D 不断地写入,B、C 不断的读取:
package main
import (
"fmt"
"sync"
"time"
)
type Like struct {
count uint8
}
func (l *Like) Add(writerID string) {
fmt.Printf("%s change count: %d\n", writerID, l.count+1)
l.count++
}
func (l *Like) Show(readerID string) {
fmt.Printf("%s read count: %d\n", readerID, l.count)
}
func AddLikes(writerID string, like *Like) {
for i := 0; i < 100; i++ {
like.Add(writerID)
}
}
func ReadLikes(readerID string, like *Like) {
for i := 0; i < 200; i++ {
like.Show(readerID)
}
}
func main() {
like := new(Like)
go AddLikes("A", like)
go ReadLikes("B", like)
go ReadLikes("C", like)
go AddLikes("D", like)
time.Sleep(10 * time.Second) //等待goroutine执行完毕
}
执行後会发现当 A、D 改变 like.count 之後,B、C 并不同步,如图 D 已经将 like.count 改成 27,但 B 其中一次读取是 20,C 其中一次读取也是 20
在写入处Add()
与读取处Show()
中加入sync.Mutex{}
的 lock,使读写不同时进行,如图:
package main
import (
"fmt"
"sync"
"time"
)
type Like struct {
sync.Mutex
count uint16
}
func (l *Like) Add(writerID string) {
l.Lock()
defer l.Unlock()
fmt.Printf("%s change count: %d\n", writerID, l.count+1)
l.count++
}
func (l *Like) Show(readerID string) {
l.Lock()
defer l.Unlock()
fmt.Printf("%s read count: %d\n", readerID, l.count)
}
func AddLikes(writerID string, like *Like) {
for i := 0; i < 100; i++ {
like.Add(writerID)
}
}
func ReadLikes(readerID string, like *Like) {
for i := 0; i < 200; i++ {
like.Show(readerID)
}
}
func main() {
like := new(Like)
go AddLikes("A", like)
go ReadLikes("B", like)
go ReadLikes("C", like)
go AddLikes("D", like)
time.Sleep(10 * time.Second) //等待goroutine执行完毕
}
但执行之後会发现,B、C 读取害 like.count 被改变的机会降得很低,如图:
这是因为只要 lock 存在,其他 lock 请求就必须等待,因为 B、C 争取 lock 大大降低了整个系统取得 lock 的机会,但我们仔细思考看看,B、C 真的需要竞争此 lock 吗?答案是不用的,因为 B、C 并不会改变 like.count,所以可以同时运行,系统只需注意 A、D 要改变 like.count 的其他人都读写完毕即可,整体运行的时间也会降低,如图:
将sync.Mutex{}
换成sync.RWMutex{}
读写锁,Show()
部分的 lock 换成 read-lock,如下:
package main
import (
"fmt"
"sync"
"time"
)
type Like struct {
sync.RWMutex
count uint16
}
func (l *Like) Add(writerID string) {
l.Lock()
defer l.Unlock()
fmt.Printf("%s change count: %d\n", writerID, l.count+1)
l.count++
}
func (l *Like) Show(readerID string) {
l.RLock()
defer l.RUnlock()
fmt.Printf("%s read count: %d\n", readerID, l.count)
}
func AddLikes(writerID string, like *Like) {
for i := 0; i < 100; i++ {
like.Add(writerID)
}
}
func ReadLikes(readerID string, like *Like) {
for i := 0; i < 200; i++ {
like.Show(readerID)
}
}
func main() {
like := new(Like)
go AddLikes("A", like)
go ReadLikes("B", like)
go ReadLikes("C", like)
go AddLikes("D", like)
time.Sleep(10 * time.Second) //等待goroutine执行完毕
}
B、C 多读,A、D 单写,使得写入与读取的机会都变得更有效率,如图:
>>: undefined 、 undeclared 、 null 的区别
先前花了几天的时间,终於把每次API发送前的安全规定的细碎精工给搞定了,也开了篇幅写了一些关於十六进...
可能发生的费用 云地混合的DevOps环境 AWS CodeCommit AWS CodePipel...
本篇文章同步发表在 HKT 线上教室 部落格,线上影音教学课程已上架至 Udemy 和 Youtu...
人生在世不如意,十之八九。唯一能够预测四十年後自己小孩成就的只有那十分之一,叫做一事无成。 这两样东...
哈哈, 其实拖了很久了! 今天来把最後剩下功能给补齐,修复跟移除, 只是我在看InstallView...