DAY 2:Single Threaded Execution Pattern,门就只有一个大家好好排队啊~

什麽是 Single Threaded Execution Pattern?

透过 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 对照

在 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

>>:  LeetCode解题 Day15

新新新手阅读 Angular 文件 - Component - ngOnDestroy(2) - Day26

本文内容 接续昨天 ngOnDestroy 还没有记录完的内容。 ngOnDestroy 可能没被启...

Day29: Picker controller

前言 今天要在 RecipeDetailView 中添加 Picker controller, 使其...

Day 15 CSS isolation

CSS isolation 介绍 有时候会想对不同 Component 做个别样式设定,但如果把 c...

Visited:我最喜欢的Node.js上的开源命令行工具

最近,我偶然发现了一个软件,"Visited",一个建立在Node.js上的开源...

Day02 Package的 类别

Package 通过使用packages 的模式,可以创建易於共享的模组化程序码 一个最基本的pac...