Go 临界资源的安全问题(引入同步异步处理)

业界 作者:SegmentFault 2022-06-04 12:54:50

作者:LiberHome

来源:SegmentFault  思否社区 


临界资源定义



并发环境中多个进程、线程、协程共享的资源


临界资源的特点



可能会因为并发操作导致数据出现不一致性,举个栗子,下面代码中的a及时临界资源


package main

import (
    "fmt"
    "time"
)

func main() {
    //临界资源
    a := 1
    go func() {
        a = 2
        fmt.Println("in this goroutine: a is : ", a)
    }()
    //在主goroutine中
    a = 3
    time.Sleep(1)
    fmt.Println("in the main goroutine: a is : ", a)
}


经典的售票问题,好多个窗口同时售票的门票的数量就是一个典型的临界资源安全问题,下面用4个协程模拟一下售票过程:


package main

import (
    "fmt"
    "math/rand"
    "time"
)

var ticket = 10 //the amount of the total ticket is 100
func main() {
    //这里启动4个goroutine模拟4个售票口 同时售票
    go saleTickets("ticket window1")
    go saleTickets("ticket window2")
    go saleTickets("ticket window3")
    go saleTickets("ticket window4")
    //这里为了保证 主协程 最后执行完 先用sleep (当然,也可以用同步等待组、chanel实现)
    time.Sleep(10 * time.Second)
}

func saleTickets(name string) {
    rand.Seed(time.Now().UnixNano())
    for {
        if ticket > 0 {
            time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
            fmt.Println(name, "saled: ", ticket)
            ticket--
        } else {
            fmt.Println(name, "sorry tickets are sold out")
            break
        }
    }
}


运行结果如下:


ticket window3 saled:  10
ticket window1 saled:  9
ticket window4 saled:  8
ticket window2 saled:  7
ticket window3 saled:  6
ticket window2 saled:  5
ticket window1 saled:  4
ticket window1 saled:  3
ticket window4 saled:  2
ticket window2 saled:  1
ticket window2 sorry tickets are sold out
ticket window4 saled:  0
ticket window4 sorry tickets are sold out
ticket window3 saled:  -1
ticket window3 sorry tickets are sold out
ticket window1 saled:  -2
ticket window1 sorry tickets are sold out


这里居然卖出了-2张票,这明显除了问题,问题出在哪里呢?


  • 我们假设现在只剩下最后1张票了,现在程序在主协程里面

  • 窗口4的协程拿到cpu资源,读取了剩余ticket总数为1,然后sleep,释放cpu资源

  • 窗口3的协程拿到cpu资源,发现剩余ticket总数为1(因为窗口4的协程进入sleep了,并没有在窗口3拿到cpu资源之前对ticket进行修改),然后sleep,释放cpu资源。

  • 窗口4醒了,ticket = 1 - 1 = 0

  • 窗口3醒了,ticket = 0 - 1 = -1


针对这种问题,可以通过上锁,在某一时间段只允许一个goroutine来访问这个共享数据,访问完毕,解锁之后,其他goroutine才能访问的方式解决。

不过有意思的是,go并不鼓励这样以共享的方式去通信,而是以通信的方式去共享【也就是不鼓励用sync包上锁,鼓励使用chanel】


资源参考:https://www.bilibili.com/video/BV1jJ411c7s3



点击左下角阅读原文,到 SegmentFault 思否社区 和文章作者展开更多互动和交流,扫描下方”二维码“或在“公众号后台回复“ 入群 ”即可加入我们的技术交流群,收获更多的技术文章~

- END -


关注公众号:拾黑(shiheibook)了解更多

赞助链接:

关注数据与安全,洞悉企业级服务市场:https://www.ijiandao.com/
四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/

公众号 关注网络尖刀微信公众号
随时掌握互联网精彩
赞助链接