聊聊Golang中形形色色的同步原语
source link: https://vearne.cc/archives/39631
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
版权声明 本站原创文章 由 萌叔 发表
转载请注明 萌叔 | http://vearne.cc
注意: 本文基于Golang 1.17.1
阅读Golang的源码的话,会发现里面有形形色色的同步原语
,发挥着重要的作用。这篇文章,萌叔打算把它们之间的依赖关系,以及与Linux的同步原语
的对应关系整理一下。
2. 与Linux系统的同步原语
之间的对应关系
锁类型 | Golang | Linux |
---|---|---|
互斥锁 | runtime.mutex (低阶) sync.Mutex (高阶) |
#include <pthread.h> int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_trylock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex); |
信号量 | runtime.semaphore | #include <sys/sem.h> int semget(key_t key, int nsems, int semflg); int semctl(int semid, int semnum, int cmd, …); int semop(int semid, struct sembuf *sops, size_t nsops); |
条件变量 | sync.Cond | #include <pthread.h> int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); int pthread_cond_signal(pthread_cond_t *cond); int pthread_cond_broadcast(pthread_cond_t *cond); |
3.依赖关系
3.1 runtime.mutex
func lock(l *mutex) {
lockWithRank(l, getLockRank(l))
}
func unlock(l *mutex) {
unlockWithRank(l)
}
注意:
1)红色部分是Linux提供的系统调用futex, 全称是fast user-space locking
2)只有在互斥锁发生竞争的场景,才会真的执行futex
相关的系统调用。
3.2 runtime.semaphore
//go:linkname sync_runtime_Semacquire sync.runtime_Semacquire
func sync_runtime_Semacquire(addr *uint32) {
semacquire1(addr, false, semaBlockProfile, 0)
}
//go:linkname sync_runtime_Semrelease sync.runtime_Semrelease
func sync_runtime_Semrelease(addr *uint32, handoff bool, skipframes int) {
semrelease1(addr, handoff, skipframes)
}
下图中
1)Treap是树堆,是一种平衡二叉树。
2)runtime.gopark()用于协程挂起
3)runtime.goready()把协程的状态从_Gwaiting
变成_Grunnable
,并放入就绪队列。
注意:
1) runtime.semaphore
依赖低阶同步原语runtime.mutex
。
2) runtime.semaphore
可能会引入自旋
3.3 sync.Mutex
注意:
1) sync.Mutex
依赖低阶同步原语runtime.semaphore
。
2) sync.Mutex
可能会引入自旋
3.4 sync.Cond
注意:
1) sync.Cond
的成员变量L
是接口,并通常使用sync.Mutex
2) sync.Cond
依赖低阶同步原语runtime.mutex
。
仔细的看完Golang的各种同步原语后,萌叔有几个感受
4.1 Golang中的同步原语大多数能够找到对应的Linux系统调用
由于Linux系统调用通常开销很大,为了实现中极力的避免使用Linux系统调用,Golang在语言层面(用户空间)重新实现了这些系统调用。
由于Golang的runtime实现了对协程的调度,因此通过使用gopark()
和goready()
等函数,即可挂起协程或者换入协程,同样达到同步的效果。因此大多数情况,Golang中同步原语的操作是完全不需要内核的介入的。
4.2 高阶的同步原语的实现依赖低阶的同步原语
就像在3.4图中看到的sync.Cond
依赖sync.Mutex
和runtime.mutex
。高阶同步原语的实现往往依赖低阶同步原语。其中在Linux中,高阶同步原语也是依赖低阶同步原语的,只不过这些高阶同步原语使用的场景相对比较特殊而已。
5. 参考资料
1.futex
2.详解linux多线程——互斥锁、条件变量、读写锁、自旋锁、信号量
3.mutex
4.sched_yield
5.go中semaphore(信号量)源码解读
6.semget
7.semaphore的原理与实现
8.pthread_cond_wait
Golang为了避免使用锁,还有一些独特的技巧。比如同一个P上的多个G,竞争访问某些临界数据时,可以完全不用加锁。
- P上的多个G共用一个runq (runnable queue存放就绪状态的G)
- P上的多个G共用一个pool (sync.Pool)
请我喝瓶饮料
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK