Bruce Blog Bruce Blog
首页
  • CentOS
  • Ubuntu-Debian
  • 系统网络
  • 系统辅助工具
  • MySQL
  • Redis
  • Mongodb
  • Docker基础
  • Container基础
  • Kubernetes

    • Kubernetes基础
    • Kubernetes辅助
  • Container-Network
  • Jenkins
  • Gitlab
  • ArgoCD
  • Ansible
  • Terraform
  • AWS
  • MQ
  • NGINX
  • JumpServer
  • 基础
  • 函数模块
  • 框架
  • 基础

    • Golang环境
    • 语法
    • 数据类型与运算符
    • 分支语句
    • 循环语句
    • 数组
    • 切片
    • Map
    • String
    • 函数
    • 包的管理
    • 指针
    • 结构体
    • Go语言中的OOP
    • 方法和接口
    • 错误处理
  • Go进阶

    • Go进阶
  • Go框架

    • Go框架
  • Golang辅助

    • Golang辅助
  • CSS
  • HTML
  • JavaScript
  • 前端辅助
  • 常用命令
  • 性能监控工具
  • Windows下Docker使用
  • 日常学习
  • 其他导航

Bruce Tao

运维界的该溜子
首页
  • CentOS
  • Ubuntu-Debian
  • 系统网络
  • 系统辅助工具
  • MySQL
  • Redis
  • Mongodb
  • Docker基础
  • Container基础
  • Kubernetes

    • Kubernetes基础
    • Kubernetes辅助
  • Container-Network
  • Jenkins
  • Gitlab
  • ArgoCD
  • Ansible
  • Terraform
  • AWS
  • MQ
  • NGINX
  • JumpServer
  • 基础
  • 函数模块
  • 框架
  • 基础

    • Golang环境
    • 语法
    • 数据类型与运算符
    • 分支语句
    • 循环语句
    • 数组
    • 切片
    • Map
    • String
    • 函数
    • 包的管理
    • 指针
    • 结构体
    • Go语言中的OOP
    • 方法和接口
    • 错误处理
  • Go进阶

    • Go进阶
  • Go框架

    • Go框架
  • Golang辅助

    • Golang辅助
  • CSS
  • HTML
  • JavaScript
  • 前端辅助
  • 常用命令
  • 性能监控工具
  • Windows下Docker使用
  • 日常学习
  • 其他导航
  • 基础

  • Go进阶

    • Go进阶
    • File操作
    • IO操作
    • 文件复制
    • 断点续传
    • bufio包
    • ioutil包
    • 遍历目录
    • 并发性Concurrency概念
    • Goroutine初识
    • Goroutine并发模型
    • Runtime包
    • 临界资源安全问题
      • sync包WaitGroup
      • 互斥锁
      • 读写锁
      • Channel通道
      • 关闭通道和通道上范围循环
      • 缓冲通道
      • 定向通道
      • time包中的通道相关函数
      • select语句
      • CSP并发模型
      • Go语言反射(一)
      • Go语言反射(二)
    • Go框架

    • Golang辅助

    • Golang
    • Go进阶
    Bruce
    2022-12-03
    目录

    临界资源安全问题

    # 临界资源安全问题

    # 一、临界资源

    临界资源: 指并发环境汇总多个进程/线程/协程共享的资源.

    但是在并发编程中对临界资源的处理不当,往往会导致数据不一致.

    示例代码:

    package main
    
    import (
        "fmt"
        "time"
    )
    
    func main()  {
        a := 1
        go func() {
            a = 2
            fmt.Println("子goroutine。。",a)
        }()
        a = 3
        time.Sleep(1)
        fmt.Println("main goroutine。。",a)
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17

    低版本go执行

    image-20221123233548691

    高版本go执行

    image-20221123224515103

    能够发现一处被多个goroutine共享的数据.

    # 二、临界资源安全问题

    并发本身并不复杂,但是因为有力资源竞争的问题,就使得开发出好的并发程序变得复杂起来,因为会引发很多莫名其妙的问题.

    如果多个goroutine在访问一个数据资源的时候,其中一个线程修改了数据,那么这个数值就被修改了,对于其他的goroutine来讲,这个数值可能是不对的.

    举个例子,通过并发来实现或者售票这个程序.一共有100张票,4个售票窗口同时出售.

    示例代码:

    package main
    
    import (
        "fmt"
        "math/rand"
        "time"
    )
    
    //全局变量
    var ticket = 10 // 100张票
    
    func main() {
        /*
        4个goroutine,模拟4个售票口,4个子程序操作同一个共享数据。
         */
        go saleTickets("售票口1") // g1,100
        go saleTickets("售票口2") // g2,100
        go saleTickets("售票口3") //g3,100
        go saleTickets("售票口4") //g4,100
    
        time.Sleep(5*time.Second)
    }
    
    func saleTickets(name string) {
        rand.Seed(time.Now().UnixNano())
        //for i:=1;i<=100;i++{
        //  fmt.Println(name,"售出:",i)
        //}
        for { //ticket=1
            if ticket > 0 { //g1,g3,g2,g4
                //睡眠
                time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
                // g1 ,g3, g2,g4
                fmt.Println(name, "售出:", ticket)  // 1 , 0, -1 , -2
                ticket--   //0 , -1 ,-2 , -3
            } else {
                fmt.Println(name,"售罄,没有票了。。")
                break
            }
        }
    }
    
    
    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
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42

    image-20221123225827000

    分析:

    买票逻辑是先判断票数的编号是否为负数,如果大于0,然后就进行买票,只不过在买票前先睡眠,然后再卖,假如说此时已经买票到只剩最后1张了,某一个goroutine持有了CPU的时间片,那么它在片段是否有票的时候,条件是成立的,所以它可以买票变为1的最后一张票.但是因为它在卖出之前,先睡眠了,那么其他的goroutine就会持有CPU的时间片,而此时这张票还没有卖出,那么第二个goroutine再判断是否有票的时候,条件也是成立的,那么它可以卖出这张票,然而它也进入了睡眠...其他的第三个第四个goroutine都是这样的逻辑,当某个goroutine醒来的时候,不会再判断是否有票,而是直接售出,这样就卖出最后一张票了,然而其他的goroutine醒来的时候,就会陆续卖出了第0张,-1张,-2张.

    这就是临界资源不安全的问题。某一个goroutine在访问某个数据资源的时候,按照数值,已经判断好了条件,然后又被其他的goroutine抢占了资源,并修改了数值,等这个goroutine在继续访问这个数据的时候,数值已经不对了.

    # 三、临界资源安全问题的解决

    要想解决临界资源的安全问题,很多编程语言的解决方案都是同步.通过上锁的方式,某一时间段,只能允许一个goroutine来访问这个共享数据,当前goroutine访问完毕,解锁后,其他的goroutine才能来访问.

    可以借助于sync包下的锁操作.

    示例代码:

    package main
    
    import (
        "fmt"
        "math/rand"
        "time"
        "sync"
    )
    
    //全局变量
    var ticket = 10 // 100张票
    
    var wg sync.WaitGroup
    var matex sync.Mutex // 创建锁头
    
    func main() {
        /*
        4个goroutine,模拟4个售票口,4个子程序操作同一个共享数据。
         */
        wg.Add(4)
        go saleTickets("售票口1") // g1,100
        go saleTickets("售票口2") // g2,100
        go saleTickets("售票口3") //g3,100
        go saleTickets("售票口4") //g4,100
        wg.Wait()              // main要等待。。。
    
        //time.Sleep(5*time.Second)
    }
    
    func saleTickets(name string) {
        rand.Seed(time.Now().UnixNano())
        defer wg.Done()
        //for i:=1;i<=100;i++{
        //  fmt.Println(name,"售出:",i)
        //}
        for { //ticket=1
            matex.Lock()
            if ticket > 0 { //g1,g3,g2,g4
                //睡眠
                time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
                // g1 ,g3, g2,g4
                fmt.Println(name, "售出:", ticket) // 1 , 0, -1 , -2
                ticket--                         //0 , -1 ,-2 , -3
            } else {
                matex.Unlock() //解锁
                fmt.Println(name, "售罄,没有票了。。")
                break
            }
            matex.Unlock() //解锁
        }
    }
    
    
    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
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52

    image-20221123232004139

    # 四、总结

    在Go的并发编程中有一句很经典的话: 不要以共享的方式去通信,而要以通信的方式去共享内存.

    在Go语言中并不鼓励用锁保护共享状态的方式在不同的Goroutine中分享信息(以共享内存的方式去通信).而是鼓励通过channel将共享状态或共享状态的变化在各个Goroutine之间传递(一通信的方式去共享内存),这样同样能像用锁一样保证在同一个Goroutine访问共享状态.

    当然,在主流的编程语言中为了保证多线程之间共享数据安全性和一致性,都会提供一套基本的同步工具集,如锁,条件变量,原子操作等等.Go语言标准库也毫不意外的提供了这些同步机制,使用方式也和其他语言差不多.

    上次更新: 2024/04/09, 16:48:42
    Runtime包
    sync包WaitGroup

    ← Runtime包 sync包WaitGroup→

    最近更新
    01
    AWS NAT-NetWork-Firwalld配置(一)
    04-09
    02
    AWS NAT-NetWork-Firwalld配置(二)
    04-09
    03
    kubernetes部署minio对象存储
    01-18
    更多文章>
    Theme by Vdoing | Copyright © 2019-2024 Bruce Tao Blog Space | MIT License
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式