go 并发安全

2021-09-07  本文已影响0人  quanCN

竞争状态

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

并发不安全示例

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之间必须同时完成发送和接收。通道会阻塞发送和接收动作的条件也会不同。只有在通道中没有要接收的值时,接收动作才会阻塞。只有在通道没有可用的缓冲区容纳被发送的值时,发送动作才会阻塞

常见操作
无/有缓冲通道区别

无缓冲的通道保证进行发送和接收的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
    }
}
上一篇 下一篇

猜你喜欢

热点阅读