透过 lock,只会有一个 goroutine 执行此区段的程序码
当设计一个点赞系统上线,允许一人有多个赞,此时会有多个 Client 进行点赞,系统需确保点赞数量正确无误。
有可能会发生,A、B、C、D 四人同时点赞,由於 A 在读取赞数後,A 还没写入 B 又进行了读取,随後 A 加一写入後,B 现有的资料还是未加一的,在 B 写入时就把 A 加的一覆盖,导致赞数少加一,如图:
相关的 code 在Github - go-design-patterns。
四位使用者各按 10000 次赞,预期会统计到 40000 个赞,
package main
import (
"fmt"
"time"
)
type Like struct {
Count uint16
}
func (l *Like) Add(writerID string) {
l.Count++
}
func AddLikes(writerID string, like *Like) {
for i := 0; i < 10000; i++ {
like.Add(writerID)
}
}
func main() {
like := new(Like)
go AddLikes("A", like)
go AddLikes("B", like)
go AddLikes("C", like)
go AddLikes("D", like)
time.Sleep(1 * time.Second) //等待goroutine执行完毕
fmt.Println(like.Count)
}
结果只有 28366 个赞:
$ go run problem.go
28366
由於like.Add()
并没有 goroutine safe(执行绪安全),造成了读写like.Count
产生了 race condition,即四个 goroutine 会同时读写 count,导致有 goroutine 在写入後,其他 goroutine 却没有读到最新资料的状况。
可以透过在 like.Add()
里新增 lock 来确保 function只会被一个goroutine读写
,当一个 goroutine 取得 like.Add()
的 lock,即有权限执行此 function,除非此 goroutine unlock,让其他 goroutine 有机会取得 lock,否则其他 goroutine 只能等待,如图:
package main
import (
"fmt"
"sync"
"time"
)
type Like struct {
sync.Mutex
count uint16
}
func (l *Like) Add(writerID string) {
l.Lock()
defer l.Unlock()
l.count++
fmt.Printf("%s change count: %d\n", writerID, l.count)
}
func AddLikes(writerID string, like *Like) {
for i := 0; i < 10000; i++ {
like.Add(writerID)
}
}
func main() {
like := new(Like)
go AddLikes("A", like)
go AddLikes("B", like)
go AddLikes("C", like)
go AddLikes("D", like)
time.Sleep(1 * time.Second) //等待goroutine执行完毕
}
在 java 中提供了更方便的 synchronized,让 lock 机制可以只加一个关键字就解决,
public class Like {
private int count = 0;
public synchronized void add() {
this.count++;
System.out.println("like count: " + this.count);
}
}
而 golang 没有 synchronized 关键字,是透过sync.Mutex
直接透过操作 lock 的方式。在 Java 的设计中提倡以 synchronized 来让程序更安全,因为直接使用 lock 可能会在 function 结束後忘记 unlock 造成 dead lock,那 golang 该如何安全的使用 lock 呢?
golang 靠 defer 解决忘记 unlock 的问题,在lock()
当下我们可以在下一行程序码加入defer Unlock()
,这样 Golang 在 compile 後就会在 function 的 return 处之前都加上 Unlock()
,藉此达到与 synchronized 一样方便安全的效果。defer 也不只用在 lock,凡要在 return 前执行的动作都可以用 defer,泛用性很高。
<<: Day 1:AWS 是什麽?30天从动漫/影视作品看AWS服务应用 -《Vivy -Fluorite Eye's Song》Part 1
本文内容 接续昨天 ngOnDestroy 还没有记录完的内容。 ngOnDestroy 可能没被启...
前言 今天要在 RecipeDetailView 中添加 Picker controller, 使其...
CSS isolation 介绍 有时候会想对不同 Component 做个别样式设定,但如果把 c...
最近,我偶然发现了一个软件,"Visited",一个建立在Node.js上的开源...
Package 通过使用packages 的模式,可以创建易於共享的模组化程序码 一个最基本的pac...