惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
L
Lohrmann on Cybersecurity
aimingoo的专栏
aimingoo的专栏
V
V2EX
S
Security Affairs
T
Threatpost
C
CXSECURITY Database RSS Feed - CXSecurity.com
IT之家
IT之家
J
Java Code Geeks
The Register - Security
The Register - Security
U
Unit 42
C
CERT Recently Published Vulnerability Notes
月光博客
月光博客
A
About on SuperTechFans
H
Hackread – Cybersecurity News, Data Breaches, AI and More
T
The Blog of Author Tim Ferriss
Cisco Talos Blog
Cisco Talos Blog
Project Zero
Project Zero
S
Schneier on Security
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
D
DataBreaches.Net
博客园 - 司徒正美
V
Vulnerabilities – Threatpost
T
Tor Project blog
Security Latest
Security Latest
T
The Exploit Database - CXSecurity.com
T
Threat Research - Cisco Blogs
Scott Helme
Scott Helme
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
M
MIT News - Artificial intelligence
云风的 BLOG
云风的 BLOG
小众软件
小众软件
L
LangChain Blog
Attack and Defense Labs
Attack and Defense Labs
Recent Commits to openclaw:main
Recent Commits to openclaw:main
P
Palo Alto Networks Blog
A
Arctic Wolf
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
C
Cyber Attacks, Cyber Crime and Cyber Security
博客园 - 叶小钗
D
Darknet – Hacking Tools, Hacker News & Cyber Security
L
LINUX DO - 最新话题
MongoDB | Blog
MongoDB | Blog
Webroot Blog
Webroot Blog
H
Hacker News: Front Page
Know Your Adversary
Know Your Adversary
Spread Privacy
Spread Privacy
AWS News Blog
AWS News Blog
Engineering at Meta
Engineering at Meta

蛮荆

如何获取更多的免费服务器 Kubernetes 调度器队列 - 设计与实现 Kubernetes 调度器 - 核心流程 Kubernetes Networking Model & CNI Kubernetes 控制器管理总结 Kubernetes CronJob 设计与实现 Kubernetes Job 设计与实现 Kubernetes HPA 设计与实现 Kubernetes Deployment 滚动更新实现原理 Kubernetes GC 设计与实现 Kubernetes Pod 驱逐 - 设计与实现 Kubernetes Daemonset 设计与实现 Kubernetes ReplicaSet 设计与实现 Kubernetes EndPoint 设计与实现 Kubernetes Informer 设计与实现 降本增效之应用优化 (三) 日志存储与检索 Kubernetes Pod 设计与实现 - 创建流程 Kubernetes 探针设计与实现 Unix 编程艺术名句摘录 Kubernetes - CRI 概述 Golang 编译速度为什么这么快? Kubernetes Pod 设计与实现 - Pause 容器 Kubernetes - kube-proxy 代理模式工程优化 Kubernetes 应用最佳实践 - 优雅关闭长连接 Kubernetes Service 类型和会话亲和性 Kubernetes 为什么需要 Ingress Kubernetes 架构 - 控制平面和数据平面 降本增效之应用优化 (二) 大报表 Go 语言如何获取 CPU 利用率 降本增效之应用优化 (一) Redis 业务规则引擎演变过程简述 微服务中的熔断算法 漏桶算法和令牌桶算法 jsonparser 为什么比标准库的 encoding/json 快 10 倍 ? zap 高性能设计与实现 HTTP Router 算法演进 fastcache 高性能设计与实现 Web 常见的三个安全问题 ants Code Reading Go 线程安全 map 方案选型 布隆过滤器 死锁、活锁、饥饿、自旋锁 sync.Pool Code Reading Go 内存管理概述 Go netpoll Code Reading goroutine 泄漏与检测 time/Timer Code Reading GMP Scheduler Code Reading Go channel 的 15 条规则和底层实现 为什么 Linux “一切皆文件” context.Context Code Reading runtime/HACKING.md Goland 最佳实践 互联网开发与金庸武学 为什么 Redis 6.0 引入多线程模型? Kubernetes 应用最佳实践 - 金丝雀发布 容器中如何正确配置 GOMAXPROCS ? singleflight Code Reading sync.Map Code Reading sync.Cond Code Reading sync.WaitGroup Code Reading sync.Mutex Code Reading sync.Once Code Reading Go 无锁编程 sync/atomic Code Reading goroutine 交替打印奇偶数 GODEBUG Go 并发模式 Go 汇编 UUID 通用技术选型 Kubernetes 应用最佳实践 - 水平自动伸缩 Go 高性能 Tips fasthttp 为什么比标准库 net/http 快 10 倍 ? 技术文章配图指南 ChatGPT 初体验 Docker 网络原理概览 iptables 的五表五链 Kubernetes 应用最佳实践 - 亲和性和污点容忍度 Go 的反射与三大定律 Docker 官方提供的最佳实践 Go 语言内置的设计模式 HTTP1 到 HTTP3 的工程优化 Kubernetes 应用最佳实践 - Sidecar 模式 Kubernetes 应用最佳实践 - init 容器和钩子函数 为什么 recover 必须在 defer 中调用? 为什么 defer 的执行顺序和注册顺序不同? Go map 设计与实现 Go 切片扩容底层实现 Go 语言中的零拷贝 Go Delve 云原生和边缘计算简介 Kubernetes Pod 服务质量等级 Kubernetes 应用最佳实践 - 探针 Kubernetes 应用最佳实践 - 资源请求和限制 CDN 原理 Kubernetes 应用最佳实践 - 开篇 缓存策略和模式 Go 内存模型 Kubernetes 核心概念 软件版本语义化
sync.RWMutex Code Reading
2023-04-24 · via 蛮荆

2023-04-24 Golang 并发编程 Go 源码分析 读代码

概述

sync.RWMutex 实现了读写锁同步原语,资源可以被并发读,但是无法并发写、并发读写,比 sync.Mutex 实现的互斥锁粒度更细 (建议读者先读完 sync.Mutex Code Reading,再来读这篇,事半功倍)。

内部实现

我们来探究一下 sync.RWMutex 的内部实现,文件路径为 $GOROOT/src/sync/rwmutex.go,笔者的 Go 版本为 go1.19 linux/amd64

RWMutex 对象

RWMutex 对象表示读写锁,零值状态下表示未加锁,锁可以被任意数量的 goroutine 并发读,但是同一时刻只能被一个 goroutine 写入,RWMutex 对象一旦使用后,就不能再复制。

如果一个 goroutine 持有了读锁进行操作,此时其他的 goroutine 可能尝试获取写锁 (参与竞争),那么在读锁被释放之前,不会有其他 goroutine 获取到读锁 (避免竞争写锁的 goroutine 被饿死) 一个 goroutine 在阻塞获取写锁的过程中,会阻止其他 goroutine 获得读锁

// 根据 Go 内存模型的约束
// 第 n 次调用 Unlock 方法在第 m 次调用 Lock 方法之前同步,其中 n < m (和互斥锁规则一致)
// 对于任何 RLock 方法调用,都存在一个 n
//    第 n 次调用 Unlock 方法在调用 RLock 方法之前同步
//    而对应的调用 RUnlock 方法在第 n+1 次 次调用 Lock 方法之前同步
type RWMutex struct {
	w           Mutex  // 复用互斥锁
	writerSem   uint32 // 信号量,写等待读
	readerSem   uint32 // 信号量,读等待写
	readerCount int32  // 执行读操作的 goroutine 数量
	readerWait  int32  // 写操作被阻塞时,等待的读操作 goroutine 数量
}

TryRLock 方法

TryLock 方法尝试获取读锁并且返回是否成功 (调用非阻塞)。

func (rw *RWMutex) TryRLock() bool {
    ...
	
	for {
		c := atomic.LoadInt32(&rw.readerCount)
		if c < 0 {
			// 执行读操作的 goroutine 数量小于 0
			// 说明当前有 goroutine 持有写锁正在操作
			return false
		}
		if atomic.CompareAndSwapInt32(&rw.readerCount, c, c+1) {
			// 获取到读锁
			return true
		}
	}
}

RLock 方法

RLock 方法用于获取读锁 (阻塞调用)。

func (rw *RWMutex) RLock() {
    ...
	
	if atomic.AddInt32(&rw.readerCount, 1) < 0 {
		// 当前 goroutine 陷入休眠,等待持有写锁的 goroutine 释放锁
		runtime_SemacquireMutex(&rw.readerSem, false, 0)
	}

	...
}

RUnlock 方法

RUnlock 方法释放读锁,不会影响到其他同时持有读锁的 goroutine

func (rw *RWMutex) RUnlock() {
    ...
	
	if r := atomic.AddInt32(&rw.readerCount, -1); r < 0 {
		// 执行读操作的 goroutine 数量小于 0
		// 说明当前有 goroutine 持有写锁正在操作
		// slow-path 允许代码内联
		rw.rUnlockSlow(r)
	}

	...
}

rUnlockSlow 方法

func (rw *RWMutex) rUnlockSlow(r int32) {
	...
	
	// 减少写锁操作时,等待读锁的 goroutine 数量
	if atomic.AddInt32(&rw.readerWait, -1) == 0 {
		// 如果所有的读锁都被释放了,唤醒等待写锁的 goroutine
		runtime_Semrelease(&rw.writerSem, false, 1)
	}
}

TryLock

TryLock 方法尝试获取写锁并且返回是否成功 (调用非阻塞),方法内部直接复用了 Mutex.TryLock 方法,这里不再赘述。

func (rw *RWMutex) TryLock() bool {}

Lock 方法

Lock 方法用于获取写锁 (阻塞调用),如果锁当前处于读或者写状态,Lock 会阻塞直到锁可用。

func (rw *RWMutex) Lock() {
    ...
	
	// 首先和等待写锁的 goroutine 竞争
	rw.w.Lock()
	
	// 获取到锁之后
	// 通知所有持有读锁的 goroutine,有 1 个 goroutine 准备写入
	//    也就是说获取到写锁之后,需要等待目前所有持有读锁的 goroutine 释放读锁后,才能继续操作
	r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders
	// 陷入休眠,等待持有读锁的 goroutine 释放锁
	if r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 {
		runtime_SemacquireMutex(&rw.writerSem, false, 0)
	}
    
	...
}

Unlock 方法

Unlock 方法释放写锁,对未加锁的读写锁执行解锁操作会引发运行时错误。

读写锁和互斥锁一样,并不是和某个具体的 goroutine 关联的,完全可以在一个 goroutine 获取读锁或写锁 ,在其他的 goroutine 中释放读锁或写锁 (前提是两者持有的是同一个锁)。

func (rw *RWMutex) Unlock() {
    ...

	// 通知持有读锁的 goroutine, 当前没有持有写锁的 goroutine 执行 
	r := atomic.AddInt32(&rw.readerCount, rwmutexMaxReaders)

	// 唤醒所有因获取读锁而陷入阻塞的 goroutine
	for i := 0; i < int(r); i++ {
		runtime_Semrelease(&rw.readerSem, false, 0)
	}
	// 释放写锁
	rw.w.Unlock()
	
    ...
}

RLocker 方法

// 将 rw 进行类型转换,返回一个 Locker 接口
func (rw *RWMutex) RLocker() Locker {
	return (*rlocker)(rw)
}

type rlocker RWMutex

func (r *rlocker) Lock()   { (*RWMutex)(r).RLock() }
func (r *rlocker) Unlock() { (*RWMutex)(r).RUnlock() }

小结

sync.RWMutex 在互斥锁的基础上,实现了粒度更细的读写锁,内部实现和互斥锁类似,通过标识符常量、原子操作再结合信号、调度等完成。

扩展阅读