关于 Go 并发编程之 Mutex


本文摘自php中文网,作者藏色散人,侵删。

下面由golang教程栏目给大家介绍 Go 并发编程之 Mutex,希望对需要的朋友有所帮助!

友情提示:此篇文章大约需要阅读 5分钟45秒,不足之处请多指教,感谢你的阅读。

我们比较常见的大型项目的设计中都会出现并发访问问题,并发就是为了解决数据的准确性,保证同一个临界区的数据只能被一个线程进行操作,日常中使用到的并发场景也是很多的:

  • 计数器:计数器结果不准确;
  • 秒杀系统:由于同一时间访问量比较大,导致的超卖;
  • 用户账户异常:同一时间支付导致的账户透支;
  • buffer 数据异常:更新 buffer 导致的数据混乱。

上面都是并发带来的数据准确性的问题,决绝方案就是使用互斥锁,也就是今天并发编程中的所要描述的 Mutex 并发原语。

实现机制

互斥锁 Mutex 就是为了避免并发竞争建立的并发控制机制,其中有个“临界区”的概念。

在并发编程过程中,如果程序中一部分资源或者变量会被并发访问或者修改,为了避免并发访问导致数据的不准确,这部分程序需要率先被保护起来,之后操作,操作结束后去除保护,这部分被保护的程序就叫做临界区

使用互斥锁,限定临界区只能同时由一个线程持有,若是临界区此时被一个线程持有,那么其他线程想进入到这个临界区的时候,就会失败或者等待释放锁,持有此临界区的线程退出,其他线程才有机会获得这个临界区。

go mutex 临界区示意图

Mutex 是 Go 语言中使用最广泛的同步原语,也称为并发原语,解决的是并发读写共享资源,避免出现数据竞争 data race 问题

基本使用

互斥锁 Mutex 提供了两个方法 Lock 和 Unlock:进入到临界区使用 Lock 方法加锁,退出临界区使用 Unlock 方法释放锁。

1

2

3

type Locker interface {

    Lock()

    Unlock()}func(m *Mutex)Lock()func(m *Mutex)Unlock()

当一个 goroutine 调用 Lock 方法获取到锁后,其他 goroutine 会阻塞在 Lock 的调用上,直到当前获取到锁的 goroutine 释放锁。

接下来是一个计数器的例子,是由 100 个 goroutine 对计数器进行累加操作,最后输出结果:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

package mainimport (

    "fmt"

    "sync")func main() {

    var mu sync.Mutex

    countNum := 0

 

    // 确认辅助变量是否都执行完成

    var wg sync.WaitGroup    // wg 添加数目要和 创建的协程数量保持一致

    wg.Add(100)

    for i := 0; i < 100; i++ {

        go func() {

            defer wg.Done()

            for j := 0; j < 1000; j++ {

                mu.Lock()

                countNum++

                mu.Unlock()

            }

        }()

    }

    wg.Wait()

    fmt.Printf("countNum: %d", countNum)}

实际使用

很多时候 Mutex 并不是单独使用的,而是嵌套在 Struct 中使用,作为结构体的一部分,如果嵌入的 struct 有多个字段,我们一般会把 Mutex 放在要控制的字段上面,然后使用空格把字段分隔开来。

甚至可以把获取锁、释放锁、计数加一的逻辑封装成一个方法。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

package mainimport (

    "fmt"

    "sync")// 线程安全的计数器type Counter struct {

    CounterType int

    Name        string

 

    mu    sync.Mutex

    count uint64}// 加一方法func (c *Counter) Incr() {

    c.mu.Lock()

    defer c.mu.Unlock()

    c.count++}// 取数值方法 线程也需要受保护func (c *Counter) Count() uint64 {

    c.mu.Lock()

    defer c.mu.Unlock()

    return c.count}func main() {

    // 定义一个计数器

    var counter Counter    var wg sync.WaitGroup

    wg.Add(100)

 

    for i := 0; i < 100; i++ {

        go func() {

            defer wg.Done()

            for j := 0; j < 1000; j++ {

                counter.Incr()

            }

        }()

    }

    wg.Wait()

 

    fmt.Printf("%d\n", counter.Count())}

思考问题

Q:你已经知道,如果 Mutex 已经被一个 goroutine 获取了锁,其它等待中的 goroutine 们只能一直等待。那么,等这个锁释放后,等待中的 goroutine 中哪一个会优先获取 Mutex 呢?

A:FIFO,先来先服务的策略,Go 的 goroutine 调度中,会维护一个保障 goroutine 运行的队列,当获取到锁的 goroutine 执行完临界区的操作的时候,就会释放锁,在队列中排在第一位置的 goroutine 会拿到锁进行临界区的操作。

以上就是关于 Go 并发编程之 Mutex的详细内容,更多文章请关注木庄网络博客!!

相关阅读 >>

关于在c++中结构体与json字符串互转的问题

Go 语言环境安装

聊聊dubbo-Go-proxy的consulregistryload

Golang字符串重新编码

php的局限性怎么破?php与Go可完美结合

聊聊cortex的backoff

Go语言学习10-指针类型

markdown 自定义的思考

聊聊dubbo-Go-proxy的loggerfilter

Go语言学习4-数组类型

更多相关阅读请进入《Go》频道 >>




打赏

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码打赏,您说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

分享从这里开始,精彩与您同在

评论

管理员已关闭评论功能...

    正在狠努力加载,请稍候...