一、线程安全
现在有两个协程,同时触发,一个协程对一个全局变量进行100完成++操作,另一个对全局变量—的操作
那么,两个协程结束,最后的值应该是0才对
package main
import (
"fmt"
"sync"
)
var num int
var wait sync.WaitGroup
func add() {
for i := 0; i < 100; i++ {
num++
}
wait.Done()
}
func reduce() {
for i := 0; i < 100; i++ {
num--
}
wait.Done()
}
func main() {
wait.Add(2)
go add()
go reduce()
wait.Wait()
fmt.Println(num)
}
但是你会发现,这个输出的结果完全无法预测。
原因是:CPU的调度方法为抢占式执行,随机调度
二、同步锁
采用锁的机制来解决以上问题
以下是使用 Go语言中的锁机制来解决上述问题的代码示例:
package main
import (
"fmt"
"sync"
)
var num int
var wait sync.WaitGroup
var lock sync.Mutex
func add() {
// 加锁
lock.Lock()
for i := 0; i < 100; i++ {
num++
}
// 解锁
lock.Unlock()
wait.Done()
}
func reduce() {
// 加锁
lock.Lock()
for i := 0; i < 100; i++ {
num--
}
// 解锁
lock.Unlock()
wait.Done()
}
func main() {
wait.Add(2)
go add()
go reduce()
wait.Wait()
fmt.Println(num)
}
在这个示例中,我们使用了一个 sync.Mutex 类型的锁来保护对全局变量 num 的访问。在 add 和 reduce 函数中,我们在对 num 进行操作之前先获取锁,操作完成后再释放锁。这样可以确保在任何时刻,只有一个协程能够访问和修改 num,从而保证了线程安全。
通过使用锁机制,我们可以得到正确的输出结果,即最后 num 的值为 0。
三、线程安全下的map
当我们在一个协程函数下,读写map就会引发如下错误:
concurrent map read and map write
这个错误的意思就是map的线程安全错误
package main
import (
"fmt"
"sync"
"time"
)
var wait sync.WaitGroup
var mp = map[string]string{}
func reader() {
for {
fmt.Println(mp["time"])
}
wait.Done()
}
func writer() {
for {
mp["time"] = time.Now().Format("15:04:05")
}
wait.Done()
}
func main() {
wait.Add(2)
go reader()
go writer()
wait.Wait()
}
我们不能在并发模式下读写map,如果想实现并发状态下读写map,需要添加如下操作
给读写操作加锁
使用sync.map
3.1 读写加锁
package main
import (
"fmt"
"sync"
"time"
)
var wait sync.WaitGroup
var mp = map[string]string{}
var lock sync.Mutex
func reader() {
for {
lock.Lock()
fmt.Println(mp["time"])
lock.Unlock()
}
wait.Done()
}
func writer() {
for {
lock.Lock()
mp["time"] = time.Now().Format("15:04:05")
lock.Unlock()
}
wait.Done()
}
func main() {
wait.Add(2)
go reader()
go writer()
wait.Wait()
}
在原始的代码中,同时启动了一个读线程和一个写线程,它们并发地读写同一个 map。这会导致竞态条件,引发线程安全错误。
为了解决这个问题,添加了一个 sync.Mutex 锁。在读写 map 之前,通过调用 lock.Lock() 获取锁,读写完成后,通过调用 lock.Unlock() 释放锁。这样可以确保在同一时刻只有一个线程能够访问 map,避免了竞态条件。
通过这种方式,读线程和写线程可以安全地并发执行,而不会出现数据不一致或其他错误。
3.2 sync.map
package main
import (
"fmt"
"sync"
"time"
)
var wait sync.WaitGroup
var mp = sync.Map{}
func reader() {
for {
fmt.Println(mp.Load("time"))
}
wait.Done()
}
func writer() {
for {
mp.Store("time", time.Now().Format("15:04:05"))
}
wait.Done()
}
func main() {
wait.Add(2)
go reader()
go writer()
wait.Wait()
}