6

聊聊Golang中形形色色的同步原语

 1 year ago
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

runtime/lock_futex.go

func lock(l *mutex) {
    lockWithRank(l, getLockRank(l))
}

func unlock(l *mutex) {
    unlockWithRank(l)
}

注意:
1)红色部分是Linux提供的系统调用futex, 全称是fast user-space locking
2)只有在互斥锁发生竞争的场景,才会真的执行futex相关的系统调用。

9f2ba694-6d49-11ec-a906-1e00da114f95.png

3.2 runtime.semaphore

runtime/sema.go

//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可能会引入自旋

62918fac-6dd3-11ec-9dcb-1e00da114f95.png

3.3 sync.Mutex

注意:
1) sync.Mutex依赖低阶同步原语runtime.semaphore
2) sync.Mutex可能会引入自旋

4ee5c66e-6dd6-11ec-b958-1e00da114f95.png

3.4 sync.Cond

注意:
1) sync.Cond的成员变量L是接口,并通常使用sync.Mutex
2) sync.Cond依赖低阶同步原语runtime.mutex

ff2a9c36-6dd9-11ec-8c6c-1e00da114f95.png

仔细的看完Golang的各种同步原语后,萌叔有几个感受

4.1 Golang中的同步原语大多数能够找到对应的Linux系统调用

由于Linux系统调用通常开销很大,为了实现中极力的避免使用Linux系统调用,Golang在语言层面(用户空间)重新实现了这些系统调用。

由于Golang的runtime实现了对协程的调度,因此通过使用gopark()goready()等函数,即可挂起协程或者换入协程,同样达到同步的效果。因此大多数情况,Golang中同步原语的操作是完全不需要内核的介入的。

4.2 高阶的同步原语的实现依赖低阶的同步原语

就像在3.4图中看到的sync.Cond依赖sync.Mutexruntime.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)

请我喝瓶饮料

微信支付码

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK