美文网首页
go 并发安全

go 并发安全

作者: quanCN | 来源:发表于2021-09-07 13:22 被阅读0次

    竞争状态

    如果多个goroutine在没有相互同步的情况下,访问某个共享的资源,并试图同时读和写这个资源,就处于相互竞争的状态,这种情况被称作竞争状态(race condition

    • 消除竞争状态的办法
      • 使用Go的锁机制
        • 原子函数
        • 互斥锁(mutex)
      • 通道
        • 无缓冲通道
        • 有缓冲通道

    并发不安全示例

    package main
    
    import (
        "fmt";"runtime";"sync"
    )
    
    var counter int64
    var wg sync.WaitGroup
    
    func main() {
        wg.Add(2)
        go incCounter()
        go incCounter()
        wg.Wait()
        fmt.Println("Final Counter:",counter)
    }
    
    func incCounter()  {
        defer wg.Done()
    
        for i := 0; i < 100; i++ {
            value := counter
            //Gosched()方法会将当前goroutine从线程退出,并放回队列,这里加入该方法只是为了更好的说明并发安全
            runtime.Gosched()
            value ++
            counter = value
        }
    }
    

    锁住共享资源

    原子函数

    sync/atomic标准库包中提供的很多原子操作,如Add、Load、Store、Swap等等,详细
    例子

    package main
    import (
        "fmt";"runtime";"sync";"sync/atomic"
    )
    // var ...
    func main() {
        //...
    }
    func incCounter()  {
        defer wg.Done()
        for i := 0; i < 100; i++ {
            atomic.AddInt64(&counter,1)
            runtime.Gosched()
        }
    }
    
    互斥锁(mutex)

    互斥锁这个名字来自互斥(mutual exclusion)的概念。互斥锁用于在代码上创建一个临界区,保证同一时间只有一个goroutine可以执行这个临界区代码,使用go提供的互斥锁,sync.Mutex
    例子

    package main
    import (
        "fmt";"runtime";"sync"
    )
    //var ...
    var mutex sync.Mutex
    
    func main() {
        //...
    }
    func incCounter()  {
        defer wg.Done()
    
        for i := 0; i < 100; i++ {
            mutex.Lock()
            value := counter
            runtime.Gosched()
            value ++
            counter = value
            mutex.Unlock()
        }
    }
    

    通道

    虽然在Go语言中也能使用共享内存加互斥锁进行通信,但是Go提供了一种不同的并发模型,即通信顺序进程(Communicating sequential processes,CSP),CSP是一种消息传递模型,通过在goroutine之间传递数据来传递消息,而不是对数据进行加锁来实现同步访问。用于在goroutine之间同步和传递数据的关键数据类型叫作通道(channel)
    通道分为有缓冲通道和无缓冲通道

    无缓冲通道

    无缓冲的通道(unbuffered channel)是指在接收前没有能力保存任何值的通道。这种类型的通道要求发送goroutine和接收goroutine同时准备好,才能完成发送和接收操作。如果两个goroutine没有同时准备好,通道会导致先执行发送或接收操作的goroutine阻塞等待。这种对通道进行发送和接收的交互行为本身就是同步的。其中任意一个操作都无法离开另一个操作单独存在

    有缓冲通道

    有缓冲的通道(buffered channel)是一种在被接收前能存储一个或者多个值的通道。这种类型的通道并不强制要求goroutine之间必须同时完成发送和接收。通道会阻塞发送和接收动作的条件也会不同。只有在通道中没有要接收的值时,接收动作才会阻塞。只有在通道没有可用的缓冲区容纳被发送的值时,发送动作才会阻塞

    常见操作
    • 声明
      //无缓冲
      unbuffered := make(chan int)
      //有缓冲
      buffered := make(chan string,10)
      
    • 发送值
      buffered := make(chan string,10)
      buffered <- "hello"
      
    • 接收值
      buffered := make(chan string,10)
      buffered <- "hello"
      //value := <-buffered
      //fmt.Println(value)
      value,ok := <-buffered
      fmt.Println(value,"==",ok)
      
    无/有缓冲通道区别

    无缓冲的通道保证进行发送和接收的goroutine会在同一时间进行数据交换;有缓冲的通道没有这种保证

    通道实现并发安全记数器
    package main
    
    import (
        "fmt","sync"
    )
    
    var wg sync.WaitGroup
    
    func main() {
        chanCounter := make(chan int,1)
        wg.Add(4)
        go incCounter(chanCounter)
        go incCounter(chanCounter)
        go incCounter(chanCounter)
        go incCounter(chanCounter)
        chanCounter <- 0
        wg.Wait()
        val := <- chanCounter
        fmt.Println(val)
    }
    
    func incCounter(chanCounter chan int)  {
        defer wg.Done()
    
        for i := 0; i < 10000; i++ {
            count := <- chanCounter
            count++
            chanCounter <- count
        }
    }
    

    相关文章

      网友评论

          本文标题:go 并发安全

          本文链接:https://www.haomeiwen.com/subject/jlzuwltx.html