golang控制并发的方式有两种
var x = 0
var wg sync.WaitGroup
func add(){
for i:=0;i<5000;i++{
x=x+1
}
wg.done()
}
func main() {
wg.add(2)
go add()
go add()
wg.Wait()
}
以上这种处理线程不安全,会出现每次执行的结果不同,需要使用锁来实现
互斥锁
互斥锁是一种常用的控制狗共享资源访问的方法,它能够保证同时只有一个goroutine可以访问共享资源。Go语言中使用sync包的Mutex类型来实现互斥锁。
- Locked: 表示该Mutex是否已被锁定,0:没有锁定 1:已被锁定。
- Woken: 表示是否有协程已被唤醒,0:没有协程唤醒 1:已有协程唤醒,正在加锁过程中。
- Starving:表示该Mutex是否处理饥饿状态, 0:没有饥饿 1:饥饿状态,说明有协程阻塞了超过1ms。
- Waiter: 表示阻塞等待锁的协程个数,协程解锁时根据此值来判断是否需要释放信号量。
协程之间抢锁实际上是抢给Locked赋值的权利,能给Locked域置1,就说明抢锁成功。抢不到的话就阻塞等待Mutex.sema信号量,一旦持有锁的协程解锁,等待的协程会依次被唤醒。
加锁后,锁被其他协程占用时
waiter计数器会增加1,这时候它阻塞,知道locked变为0才可以唤醒(这是一种自旋锁)
加锁后要立即使用defer对其解锁,可以有效避免死锁
var x = 0
var wg sync.WaitGroup
var lock sync.Mutex
func add(){
for i:=0;i<5000;i++{
lock.Lock()
x=x+1
lock.Unlock()
}
wg.done()
}
func main() {
wg.add(2)
go add()
go add()
wg.Wait()
}
读写互斥锁
互斥锁是完全互斥的,但很多情况下都是读多写少的。这时候可以使用读写互斥锁RWMutex
sync.Map
map在goroutine中使用是不安全的,因此考虑使用sync.Map
- sync.Map的核心实现 - 两个map,一个用于写,一个用于读,这样的设计思路可以类比缓存与数据库
- sync.Map的局限性 - 如果写远高于读,
dirty -> readOnly
这个类似于刷数据的频率就比较高,不如直接用mutex + map
的组合 - sync.Map的设计思路,保证高频率读的无锁结构,空间换时间 ```go var m sync.Map
// 保存kv m.Store(“a”,1)
// 加载key b := m.Load(“a”)
// 有key就加载,没有key就保存 c := m.LoadOrStore(“a”,1)
// 删除kv d := m.Delete(“a”)
// 遍历,遇到false就退出 m.Range(func(key, value interface{}) bool { fmt.Println(key, value) return false })
<a name="JYss8"></a>
# Once
在某些场景下确保高并发时只执行一次操作,比如只加载一次配置文件,只关闭一次通道等。
<a name="HYkfv"></a>
## sync.Map
go语言中的map不是并发安全的,要使用sync.map()
<a name="Context"></a>
# Context
用于处理有多个`goroutine`或`goroutine`里有`goroutine`
```bash
ctx, cancel := context.WithCancel(context.Background())
go worker(ctx, "job1")
go worker(ctx, "job2")
go worker(ctx, "job3")
cancel()
多路复用select
某些场景下我们需要同时从多个通道接收数据。通道在接收数据时,如果没有数据可以接收将会阻塞。虽然可以使用死循环尝试取值,但性能并不是很好。于是go内置了select
关键字,可以同时相应多个通道的操作。
select原理:
IO多路复用,每个线程或者进程都注册,然后阻塞,当注册的线程或进程准备好数据后,会去取值,无需在对额外的线程或进程进行管理
golang的case语句是以下结构体
type scase struct {
c *hchan // chan
elem unsafe.Pointer // 读或者写的缓冲区地址
kind uint16 //case语句的类型,是default、传值写数据(channel <-) 还是 取值读数据(<- channel)
pc uintptr // race pc (for race detector / msan)
releasetime int64
}
其中hchan是channel的指针,在一个select中,所有case语句会构成一个scase结构体的数组
然后select语句就是调用func selectgo(cas0 *scase, order0 *uint16, ncases int) (int, nool)
函数
- cas0 为上文提到的case语句抽象出的结构体
scase
数组的第一个元素地址 - order0为一个两倍cas0数组长度的buffer,保存scase随机序列pollorder和scase中channel地址序列lockorder。
- nncases表示
scase
数组的长度
执行结果相当于,函数传入case语句,返回值返回一个选中的case语句。它相当于打乱所有case的实例,然后锁住channel,遍历所有channel看有无可读或可写,如果有,解锁所有channel,返回选中channel数据,如果没有,也没有default,则该goroutine进入阻塞,等待唤醒
atomic
原子处理,自动加锁(对于比较常用的操作)
读取,写入,修改,交换,比较并交换(汇编操作)