美文网首页
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语言高并发Map解决方案

    Go语言高并发Map解决方案 Go语言基础库中的map不是并发安全的,不过基于读写锁可以实现线程安全;不过在Go1...

  • go 并发安全

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

  • 2017年5月17日晨读

    1.go package 的规范指南2.go structs 的并发安全3.go tcp详解4.go tool t...

  • Go语言并发安全

    go语言最重要的特点就是原生支持并发————goroutine。而用到并发,就不得不考虑数据安全的问题。Go语言里...

  • Go Map 为什么是非线程安全的?

    Go map 默认是并发不安全的,同时对 map 进行并发读写的时,程序会 panic,原因如下:Go 官方经过长...

  • GO并发安全 Map

    场景1:一个请求同时进行2条数据库查询操作,然后把查询的数据,传给一个map,然后返回给客户端 问题代码 进行多次...

  • 通道

    通道是 Go 并发编程中重要的一员 基础知识 通道是 Go 自带的,唯一一个可以满足并发安全性的类型。 声明并初始...

  • 详尽解析go中的fatal error: concurrent

    fatal error: concurrent map writes 问题出现的原因 go中的map不是并发安全的...

  • Go语言并发

    Go语言并发 Go语言级别支持协程,叫做goroutine Go 语言从语言层面支持并发和并行的开发操作 Go并发...

  • <>

    通道的基本操作# 通道本身是并发安全的,这也是go语言自带的唯一一个可以满足并发安全的的类型。 通道的初始化 通道...

网友评论

      本文标题:go 并发安全

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