竞争状态
如果多个goroutine在没有相互同步的情况下,访问某个共享的资源,并试图同时读和写这个资源,就处于相互竞争的状态,这种情况被称作竞争状态(race condition
)
- 消除竞争状态的办法
- 使用Go的锁机制
- 原子函数
- 互斥锁(mutex)
- 通道
- 无缓冲通道
- 有缓冲通道
- 使用Go的锁机制
并发不安全示例
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
}
}
网友评论