You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

583 lines
44 KiB
HTML

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!DOCTYPE html>
<html lang="zh-cn" dir="ltr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="读写锁 - sync.RWMutex # RWMutex是Go语言中内置的一个reader/writer锁用来解决读者-写者问题Readerswriters problem。在任意一时刻一个RWMutex只能由任意数量的reader持有或者只能由一个writer持有。
读者-写者问题 # 读者-写者问题Readerswriters problem描述了计算机并发处理读写数据遇到的问题如何保证数据完整性、一致性。解决读者-写者问题需保证对于一份资源操作满足以下下条件:
读写互斥 写写互斥 允许多个读者同时读取 解决读者-写者问题可以采用读者优先readers-preference方案或者写者优先writers-preference方案。
读者优先readers-preference读者优先是读操作优先于写操作即使写操作提出申请资源但只要还有读者在读取操作就还允许其他读者继续读取操作直到所有读者结束读取才开始写。读优先可以提供很高的并发处理性能但是在频繁读取的系统中会长时间写阻塞导致写饥饿。
写者优先writers-preference写者优先是写操作优先于读操作如果有写者提出申请资源在申请之前已经开始读取操作的可以继续执行读取但是如果再有读者申请读取操作则不能够读取只有在所有的写者写完之后才可以读取。写者优先解决了读者优先造成写饥饿的问题。但是若在频繁写入的系统中会长时间读阻塞导致读饥饿。
RWMutex设计采用写者优先方法保证写操作优先处理。
源码分析 # 下面分析的源码进行精简处理去掉了race检查功能的代码。
RWMutex的定义 # type RWMutex struct { w Mutex // 互斥锁 writerSem uint32 // writers信号量 readerSem uint32 // readers信号量 readerCount int32 // reader数量 readerWait int32 // writer申请锁时候已经申请到锁的reader的数量 } const rwmutexMaxReaders = 1 &lt;&lt; 30 // 最大reader数用于反转readerCount RLock/RUnlock的实现 # func (rw *RWMutex) RLock() { if atomic.AddInt32(&amp;rw.readerCount, 1) &lt; 0 { // 如果rw.">
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#343a40">
<meta name="color-scheme" content="light dark"><meta property="og:title" content="读写锁 - sync.RWMutex" />
<meta property="og:description" content="读写锁 - sync.RWMutex # RWMutex是Go语言中内置的一个reader/writer锁用来解决读者-写者问题Readerswriters problem。在任意一时刻一个RWMutex只能由任意数量的reader持有或者只能由一个writer持有。
读者-写者问题 # 读者-写者问题Readerswriters problem描述了计算机并发处理读写数据遇到的问题如何保证数据完整性、一致性。解决读者-写者问题需保证对于一份资源操作满足以下下条件:
读写互斥 写写互斥 允许多个读者同时读取 解决读者-写者问题可以采用读者优先readers-preference方案或者写者优先writers-preference方案。
读者优先readers-preference读者优先是读操作优先于写操作即使写操作提出申请资源但只要还有读者在读取操作就还允许其他读者继续读取操作直到所有读者结束读取才开始写。读优先可以提供很高的并发处理性能但是在频繁读取的系统中会长时间写阻塞导致写饥饿。
写者优先writers-preference写者优先是写操作优先于读操作如果有写者提出申请资源在申请之前已经开始读取操作的可以继续执行读取但是如果再有读者申请读取操作则不能够读取只有在所有的写者写完之后才可以读取。写者优先解决了读者优先造成写饥饿的问题。但是若在频繁写入的系统中会长时间读阻塞导致读饥饿。
RWMutex设计采用写者优先方法保证写操作优先处理。
源码分析 # 下面分析的源码进行精简处理去掉了race检查功能的代码。
RWMutex的定义 # type RWMutex struct { w Mutex // 互斥锁 writerSem uint32 // writers信号量 readerSem uint32 // readers信号量 readerCount int32 // reader数量 readerWait int32 // writer申请锁时候已经申请到锁的reader的数量 } const rwmutexMaxReaders = 1 &lt;&lt; 30 // 最大reader数用于反转readerCount RLock/RUnlock的实现 # func (rw *RWMutex) RLock() { if atomic.AddInt32(&amp;rw.readerCount, 1) &lt; 0 { // 如果rw." />
<meta property="og:type" content="article" />
<meta property="og:url" content="https://go.cyub.vip/concurrency/sync-rwmutex/" /><meta property="article:section" content="concurrency" />
<title>读写锁 - sync.RWMutex | 深入Go语言之旅</title>
<link rel="manifest" href="/manifest.json">
<link rel="icon" href="/favicon.png" >
<link rel="stylesheet" href="/book.min.f06572240ce28e67eb332ac5cf5d59a696c47ad4c6f700d5842c5ed93dd8ec77.css" integrity="sha256-8GVyJAzijmfrMyrFz11ZppbEetTG9wDVhCxe2T3Y7Hc=" crossorigin="anonymous">
<script defer src="/flexsearch.min.js"></script>
<script defer src="/en.search.min.17ed8785d618483565ce5458241250de0bb24d7b931b8b71446036ef43affd37.js" integrity="sha256-F&#43;2HhdYYSDVlzlRYJBJQ3guyTXuTG4txRGA270Ov/Tc=" crossorigin="anonymous"></script>
<script async src="https://www.googletagmanager.com/gtag/js?id=G-BQ229RRTTX"></script>
<script>
var doNotTrack = false;
if (!doNotTrack) {
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-BQ229RRTTX', { 'anonymize_ip': false });
}
</script>
<!--
Made with Book Theme
https://github.com/alex-shpak/hugo-book
-->
</head>
<body dir="ltr">
<input type="checkbox" class="hidden toggle" id="menu-control" />
<input type="checkbox" class="hidden toggle" id="toc-control" />
<main class="container flex">
<aside class="book-menu">
<div class="book-menu-content">
<nav>
<h2 class="book-brand">
<a class="flex align-center" href="/"><img src="https://static.cyub.vip/images/202310/golang-480.png" alt="Logo" /><span>深入Go语言之旅</span>
</a>
</h2>
<div class="book-search">
<input type="text" id="book-search-input" placeholder="Search" aria-label="Search" maxlength="64" data-hotkeys="s/" />
<div class="book-search-spinner hidden"></div>
<ul id="book-search-results"></ul>
</div>
<ul>
<li>
<a href="https://www.cyub.vip/" target="_blank" rel="noopener">
个人博客
</a>
</li>
<li>
<a href="https://github.com/cyub" target="_blank" rel="noopener">
Github主页
</a>
</li>
<li>
<a href="https://www.topgoer.cn/?ref=go.cyub.vip" target="_blank" rel="noopener">
地鼠文档
</a>
</li>
</ul>
<ul>
<li>
<p><strong>
<a href="/">深入Go语言之旅</a></strong></p>
</li>
<li>
<p><strong>准备篇</strong></p>
<ul>
<li>
<a href="/compiler/">编译流程</a></li>
<li>
<a href="/analysis-tools/">分析工具</a>
<ul>
<li>
<a href="/analysis-tools/gdb/">GDB</a></li>
<li>
<a href="/analysis-tools/dlv/">Delve</a></li>
<li>
<a href="/analysis-tools/go-buildin-tools/">Go 内置工具</a></li>
</ul>
</li>
<li>
<a href="/go-assembly/">Go汇编</a></li>
</ul>
</li>
<li>
<p><strong>基础篇</strong></p>
<ul>
<li>
<a href="/type/">数据类型与数据结构</a>
<ul>
<li>
<a href="/type/string/">字符串</a></li>
<li>
<a href="/type/array/">数组</a></li>
<li>
<a href="/type/slice/">切片</a></li>
<li>
<a href="/type/nil/">nil</a></li>
<li>
<a href="/type/empty_struct/">空结构体</a></li>
<li>
<a href="/type/pointer/">指针</a></li>
<li>
<a href="/type/map/">映射</a></li>
</ul>
</li>
<li>
<a href="/function/">函数</a>
<ul>
<li>
<a href="/function/first-class/">一等公民</a></li>
<li>
<a href="/function/call-stack/">函数调用栈</a></li>
<li>
<a href="/function/pass-by-value/">值传递</a></li>
<li>
<a href="/function/closure/">闭包</a></li>
<li>
<a href="/function/method/">方法</a></li>
</ul>
</li>
<li>
<a href="/feature/">语言特性</a>
<ul>
<li>
<a href="/feature/comma-ok/">逗号ok模式</a></li>
<li>
<a href="/feature/for-range/">遍历 - for-range语法</a></li>
<li>
<a href="/feature/defer/">延迟执行 - defer语法</a></li>
<li>
<a href="/feature/select/">通道选择器 - select语法</a></li>
<li>
<a href="/feature/panic-recover/">恐慌与恢复 - panic/recover</a></li>
</ul>
</li>
</ul>
</li>
<li>
<p><strong>运行时篇</strong></p>
<ul>
<li>
<a href="/concurrency/">并发编程</a>
<ul>
<li>
<a href="/concurrency/memory-model/">内存模型</a></li>
<li>
<a href="/concurrency/context/">上下文 - context</a></li>
<li>
<a href="/concurrency/channel/">通道 - channel</a></li>
<li>
<a href="/concurrency/atomic/">原子操作 - atomic</a></li>
<li>
<a href="/concurrency/sync-map/">并发Map - sync.Map</a></li>
<li>
<a href="/concurrency/sync-waitgroup/">等待组 - sync.WaitGroup</a></li>
<li>
<a href="/concurrency/sync-once/">一次性操作 - sync.Once</a></li>
<li>
<a href="/concurrency/sync-pool/">缓冲池 - sync.Pool</a></li>
<li>
<a href="/concurrency/sync-cond/">条件变量 - sync.Cond</a></li>
<li>
<a href="/concurrency/sync-mutex/">互斥锁 - sync.Mutex</a></li>
<li>
<a href="/concurrency/sync-rwmutex/"class=active>读写锁 - sync.RWMutex</a></li>
</ul>
</li>
<li>
<a href="/gmp/">G-M-P调度机制</a>
<ul>
<li>
<a href="/gmp/gmp-model/">调度机制概述</a></li>
<li>
<a href="/gmp/scheduler/">调度器</a></li>
</ul>
</li>
<li>
<a href="/memory/">内存管理</a>
<ul>
<li>
<a href="/memory/allocator/">内存分配器</a></li>
<li>
<a href="/memory/gc/">GC</a></li>
</ul>
</li>
<li>
<a href="/type-system/">类型系统</a>
<ul>
<li>
<a href="/type-system/type/">类型系统</a></li>
<li>
<a href="/type-system/interface/">接口</a></li>
<li>
<a href="/type-system/reflect/">反射</a></li>
</ul>
</li>
</ul>
</li>
</ul>
</nav>
<script>(function(){var e=document.querySelector("aside .book-menu-content");addEventListener("beforeunload",function(){localStorage.setItem("menu.scrollTop",e.scrollTop)}),e.scrollTop=localStorage.getItem("menu.scrollTop")})()</script>
</div>
</aside>
<div class="book-page">
<header class="book-header">
<div class="flex align-center justify-between">
<label for="menu-control">
<img src="/svg/menu.svg" class="book-icon" alt="Menu" />
</label>
<strong>读写锁 - sync.RWMutex</strong>
<label for="toc-control">
</label>
</div>
</header>
<article class="markdown"><h1 id="读写锁---syncrwmutex">
读写锁 - sync.RWMutex
<a class="anchor" href="#%e8%af%bb%e5%86%99%e9%94%81---syncrwmutex">#</a>
</h1>
<p>RWMutex是Go语言中内置的一个reader/writer锁用来解决读者-写者问题Readerswriters problem。在任意一时刻一个RWMutex只能由任意数量的reader持有或者只能由一个writer持有。</p>
<h2 id="读者-写者问题">
读者-写者问题
<a class="anchor" href="#%e8%af%bb%e8%80%85-%e5%86%99%e8%80%85%e9%97%ae%e9%a2%98">#</a>
</h2>
<p>读者-写者问题Readerswriters problem描述了计算机并发处理读写数据遇到的问题如何保证数据完整性、一致性。解决读者-写者问题需保证对于一份资源操作满足以下下条件:</p>
<ul>
<li>读写互斥</li>
<li>写写互斥</li>
<li>允许多个读者同时读取</li>
</ul>
<p>解决读者-写者问题,可以采用<code>读者优先readers-preference</code>方案或者<code>写者优先writers-preference</code>方案。</p>
<ul>
<li>
<p><strong>读者优先readers-preference</strong>:读者优先是读操作优先于写操作,即使写操作提出申请资源,但只要还有读者在读取操作,就还允许其他读者继续读取操作,直到所有读者结束读取,才开始写。读优先可以提供很高的并发处理性能,但是在频繁读取的系统中,会长时间写阻塞,导致写饥饿。</p>
</li>
<li>
<p><strong>写者优先writers-preference</strong>:写者优先是写操作优先于读操作,如果有写者提出申请资源,在申请之前已经开始读取操作的可以继续执行读取,但是如果再有读者申请读取操作,则不能够读取,只有在所有的写者写完之后才可以读取。写者优先解决了读者优先造成写饥饿的问题。但是若在频繁写入的系统中,会长时间读阻塞,导致读饥饿。</p>
</li>
</ul>
<p>RWMutex设计采用写者优先方法保证写操作优先处理。</p>
<h2 id="源码分析">
源码分析
<a class="anchor" href="#%e6%ba%90%e7%a0%81%e5%88%86%e6%9e%90">#</a>
</h2>
<p>下面分析的源码进行精简处理去掉了race检查功能的代码。</p>
<h3 id="rwmutex的定义">
RWMutex的定义
<a class="anchor" href="#rwmutex%e7%9a%84%e5%ae%9a%e4%b9%89">#</a>
</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">RWMutex</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">w</span> <span style="color:#a6e22e">Mutex</span> <span style="color:#75715e">// 互斥锁
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">writerSem</span> <span style="color:#66d9ef">uint32</span> <span style="color:#75715e">// writers信号量
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">readerSem</span> <span style="color:#66d9ef">uint32</span> <span style="color:#75715e">// readers信号量
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">readerCount</span> <span style="color:#66d9ef">int32</span> <span style="color:#75715e">// reader数量
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">readerWait</span> <span style="color:#66d9ef">int32</span> <span style="color:#75715e">// writer申请锁时候已经申请到锁的reader的数量
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">rwmutexMaxReaders</span> = <span style="color:#ae81ff">1</span> <span style="color:#f92672">&lt;&lt;</span> <span style="color:#ae81ff">30</span> <span style="color:#75715e">// 最大reader数用于反转readerCount
</span></span></span></code></pre></div><h3 id="rlockrunlock的实现">
RLock/RUnlock的实现
<a class="anchor" href="#rlockrunlock%e7%9a%84%e5%ae%9e%e7%8e%b0">#</a>
</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">rw</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">RWMutex</span>) <span style="color:#a6e22e">RLock</span>() {
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">atomic</span>.<span style="color:#a6e22e">AddInt32</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">rw</span>.<span style="color:#a6e22e">readerCount</span>, <span style="color:#ae81ff">1</span>) &lt; <span style="color:#ae81ff">0</span> { <span style="color:#75715e">// 如果rw.readerCount为负数说明此时已有一个writer持有锁或者正在申请锁。
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">runtime_SemacquireMutex</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">rw</span>.<span style="color:#a6e22e">readerSem</span>, <span style="color:#66d9ef">false</span>, <span style="color:#ae81ff">0</span>) <span style="color:#75715e">// 此时reader休眠阻塞在readerSem信号上等待唤醒
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">rw</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">RWMutex</span>) <span style="color:#a6e22e">RUnlock</span>() {
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">r</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">atomic</span>.<span style="color:#a6e22e">AddInt32</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">rw</span>.<span style="color:#a6e22e">readerCount</span>, <span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>); <span style="color:#a6e22e">r</span> &lt; <span style="color:#ae81ff">0</span> { <span style="color:#75715e">// r小于0说明此时有等待请求锁的writer
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">rw</span>.<span style="color:#a6e22e">rUnlockSlow</span>(<span style="color:#a6e22e">r</span>)
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">rw</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">RWMutex</span>) <span style="color:#a6e22e">rUnlockSlow</span>(<span style="color:#a6e22e">r</span> <span style="color:#66d9ef">int32</span>) {
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">r</span><span style="color:#f92672">+</span><span style="color:#ae81ff">1</span> <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span> <span style="color:#f92672">||</span> <span style="color:#a6e22e">r</span><span style="color:#f92672">+</span><span style="color:#ae81ff">1</span> <span style="color:#f92672">==</span> <span style="color:#f92672">-</span><span style="color:#a6e22e">rwmutexMaxReaders</span> { <span style="color:#75715e">// RLock之前已经进行了RUnlock操作
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">throw</span>(<span style="color:#e6db74">&#34;sync: RUnlock of unlocked RWMutex&#34;</span>)
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">atomic</span>.<span style="color:#a6e22e">AddInt32</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">rw</span>.<span style="color:#a6e22e">readerWait</span>, <span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>) <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span> { <span style="color:#75715e">// 此时是最后一个获取到锁的reader进行RUnlock操作那么释放writerSem信号唤醒等待的writer来获取锁。
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">runtime_Semrelease</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">rw</span>.<span style="color:#a6e22e">writerSem</span>, <span style="color:#66d9ef">false</span>, <span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h3 id="lockunlock的实现">
Lock/Unlock的实现
<a class="anchor" href="#lockunlock%e7%9a%84%e5%ae%9e%e7%8e%b0">#</a>
</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">rw</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">RWMutex</span>) <span style="color:#a6e22e">Lock</span>() {
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">rw</span>.<span style="color:#a6e22e">w</span>.<span style="color:#a6e22e">Lock</span>() <span style="color:#75715e">// 加互斥锁阻塞其他writer进行Lock操作保证写-写互斥。
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span> <span style="color:#75715e">// 将rw.readerCount 更改为rw.readerCount - rwmutexMaxReaders
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#75715e">// 此时rw.readerCount由一个正数转变成一个负数这种方式既能保持记录reader数量又能表明有writer正在请求锁
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">r</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">atomic</span>.<span style="color:#a6e22e">AddInt32</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">rw</span>.<span style="color:#a6e22e">readerCount</span>, <span style="color:#f92672">-</span><span style="color:#a6e22e">rwmutexMaxReaders</span>) <span style="color:#f92672">+</span> <span style="color:#a6e22e">rwmutexMaxReaders</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">r</span> <span style="color:#f92672">!=</span> <span style="color:#ae81ff">0</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#a6e22e">atomic</span>.<span style="color:#a6e22e">AddInt32</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">rw</span>.<span style="color:#a6e22e">readerWait</span>, <span style="color:#a6e22e">r</span>) <span style="color:#f92672">!=</span> <span style="color:#ae81ff">0</span> { <span style="color:#75715e">// r!=0表明此时有reader持有锁则当前writer只能阻塞等待但为了保证写优先需要readerWait记录当前已获取到锁的读者数量
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">runtime_SemacquireMutex</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">rw</span>.<span style="color:#a6e22e">writerSem</span>, <span style="color:#66d9ef">false</span>, <span style="color:#ae81ff">0</span>)
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">rw</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">RWMutex</span>) <span style="color:#a6e22e">Unlock</span>() {
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">r</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">atomic</span>.<span style="color:#a6e22e">AddInt32</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">rw</span>.<span style="color:#a6e22e">readerCount</span>, <span style="color:#a6e22e">rwmutexMaxReaders</span>)
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">r</span> <span style="color:#f92672">&gt;=</span> <span style="color:#a6e22e">rwmutexMaxReaders</span> { <span style="color:#75715e">// Lock之前先进行了Unlock操作
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">throw</span>(<span style="color:#e6db74">&#34;sync: Unlock of unlocked RWMutex&#34;</span>)
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">i</span> &lt; int(<span style="color:#a6e22e">r</span>); <span style="color:#a6e22e">i</span><span style="color:#f92672">++</span> { <span style="color:#75715e">// 释放信号唤醒阻塞的reader们
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">runtime_Semrelease</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">rw</span>.<span style="color:#a6e22e">readerSem</span>, <span style="color:#66d9ef">false</span>, <span style="color:#ae81ff">0</span>)
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">rw</span>.<span style="color:#a6e22e">w</span>.<span style="color:#a6e22e">Unlock</span>() <span style="color:#75715e">// 是否互锁锁允许其他writer进行获取锁操作了
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>}
</span></span></code></pre></div><p>对于<code>读者优先readers-preference</code>的读写锁,只需要一个<code>readerCount</code>记录所有读者就可以轻易实现。Go中的RWMutex实现的是<code>写者优先writers-preference</code>的读写锁,那就需要用到<code>readerWait</code>来记录写者申请锁时候,已经获取到锁的读者数量。</p>
<p>这样当后续有其他读者继续申请锁时候,可以读取<code>readerWait</code>是否大于0大于0则说明有写者已经申请锁了按照<code>写者优先writers-preference</code>原则,该读者需要排到写者之后,但是我们还需要记录这些排在写者后面读者的数量呀,毕竟写着将来释放锁的时候,还得唤醒一个个这些读者。这种情况下既要读取<code>readerWait</code>又要更新排队的读者数量这是两个操作无法原子化。RWMutex在实现时候通过将readerCount转换成负数一方面表明有写者申请了锁另一方面readerCount还可以继续记录排队的读者数量解决刚描述的无法原子化的问题真是巧妙</p>
<p>对于<code>读者优先readers-preference</code>的读写锁我们可以借助Mutex实现。示例代码如下</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">rwlock</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">reader_cnt</span> <span style="color:#66d9ef">int</span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">reader_lock</span> <span style="color:#a6e22e">sync</span>.<span style="color:#a6e22e">Mutex</span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">writer_lock</span> <span style="color:#a6e22e">sync</span>.<span style="color:#a6e22e">Mutex</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">NewRWLock</span>() <span style="color:#f92672">*</span><span style="color:#a6e22e">rwlock</span> {
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">rwlock</span>{}
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">l</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">rwlock</span>) <span style="color:#a6e22e">RLock</span>() {
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">l</span>.<span style="color:#a6e22e">reader_lock</span>.<span style="color:#a6e22e">Lock</span>()
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">l</span>.<span style="color:#a6e22e">reader_lock</span>.<span style="color:#a6e22e">Unlock</span>()
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">l</span>.<span style="color:#a6e22e">reader_cnt</span><span style="color:#f92672">++</span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">l</span>.<span style="color:#a6e22e">reader_cnt</span> <span style="color:#f92672">==</span> <span style="color:#ae81ff">1</span> { <span style="color:#75715e">// first reader
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">l</span>.<span style="color:#a6e22e">writer_lock</span>.<span style="color:#a6e22e">Lock</span>()
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">l</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">rwlock</span>) <span style="color:#a6e22e">RUnlock</span>() {
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">l</span>.<span style="color:#a6e22e">reader_lock</span>.<span style="color:#a6e22e">Lock</span>()
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">l</span>.<span style="color:#a6e22e">reader_lock</span>.<span style="color:#a6e22e">Unlock</span>()
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">l</span>.<span style="color:#a6e22e">reader_cnt</span><span style="color:#f92672">--</span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">l</span>.<span style="color:#a6e22e">reader_cnt</span> <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span> { <span style="color:#75715e">// latest reader
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">l</span>.<span style="color:#a6e22e">writer_lock</span>.<span style="color:#a6e22e">Unlock</span>()
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">l</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">rwlock</span>) <span style="color:#a6e22e">Lock</span>() {
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">l</span>.<span style="color:#a6e22e">writer_lock</span>.<span style="color:#a6e22e">Lock</span>()
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">l</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">rwlock</span>) <span style="color:#a6e22e">Unlock</span>() {
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">l</span>.<span style="color:#a6e22e">writer_lock</span>.<span style="color:#a6e22e">Unlock</span>()
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>上面示例代码中,尽管读者操作的实现上用到互斥锁,但由于它是用完立马就是释放掉,性能不会差太多。</p>
<h2 id="三大错误使用场景">
三大错误使用场景
<a class="anchor" href="#%e4%b8%89%e5%a4%a7%e9%94%99%e8%af%af%e4%bd%bf%e7%94%a8%e5%9c%ba%e6%99%af">#</a>
</h2>
<h3 id="rlockrunlocklockunlock未成对出现">
RLock/RUnlock、Lock/Unlock未成对出现
<a class="anchor" href="#rlockrunlocklockunlock%e6%9c%aa%e6%88%90%e5%af%b9%e5%87%ba%e7%8e%b0">#</a>
</h3>
<p>同互斥锁一样sync.RWMutex的RLock/RUnlock以及Lock/Unlock总是成对出现的。Lock或RLock多余调用会导致锁没有释放可能出现死锁Unlock或RUnlock多余的调用会大导致panic.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">l</span> <span style="color:#a6e22e">sync</span>.<span style="color:#a6e22e">RWMutex</span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">l</span>.<span style="color:#a6e22e">Lock</span>()
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">l</span>.<span style="color:#a6e22e">Unlock</span>()
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">l</span>.<span style="color:#a6e22e">Unlock</span>() <span style="color:#75715e">// fatal error: sync: Unlock of unlocked RWMutex
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>}
</span></span></code></pre></div><p>对于Lock/Unlock未成对出现所有可能情况如下</p>
<ul>
<li>
<p>如果只有Lock情况</p>
<p>如果有一个 goroutine 只执行 Lock 操作而不执行 Unlock 操作,那么其他的 goroutine 就会一直被阻塞(拿不到锁),随着越来越多的阻塞的 goroutine 越来越多,整个系统最终会崩溃。</p>
</li>
<li>
<p>如果只有Unlock情况</p>
<ul>
<li>如果其他 goroutine 持有锁,锁将被释放。</li>
<li>如果锁处于空闲状态unoccupied state它会panic。</li>
</ul>
</li>
</ul>
<h3 id="复制syncrwmutex作为函数值传递">
复制sync.RWMutex作为函数值传递
<a class="anchor" href="#%e5%a4%8d%e5%88%b6syncrwmutex%e4%bd%9c%e4%b8%ba%e5%87%bd%e6%95%b0%e5%80%bc%e4%bc%a0%e9%80%92">#</a>
</h3>
<p>同Mutex一样RWMutex也是不能复制使用的考虑下面场景代码</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">l</span> <span style="color:#a6e22e">sync</span>.<span style="color:#a6e22e">RWMutex</span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">l</span>.<span style="color:#a6e22e">Lock</span>()
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">foo</span>(<span style="color:#a6e22e">l</span>)
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">l</span>.<span style="color:#a6e22e">Lock</span>()
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">l</span>.<span style="color:#a6e22e">Unlock</span>()
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">foo</span>(<span style="color:#a6e22e">l</span> <span style="color:#a6e22e">sync</span>.<span style="color:#a6e22e">RWMutex</span>) {
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">l</span>.<span style="color:#a6e22e">Unlock</span>()
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>上面场景代码中本意先使用l.Lock()进行上锁操作然后调用foo(l)释放该锁最后再次上锁和释放锁。但这种操作是错误的会导致死锁。foo()函数接收的参数是变量l的一个副本该副本把之前l变量的锁状态锁状态指的是writerSemreaderCount等字段信息也复制了一遍此时副本的锁状态是上锁状态的所以foo函数中是可以进行释放锁操作的但释放的并不是最开始的那个锁。</p>
<p>我们可以使用<code>go vet</code>命令检测复制锁情况:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>vagrant@vagrant:~$ go vet main.go
</span></span><span style="display:flex;"><span><span style="color:#75715e"># command-line-arguments</span>
</span></span><span style="display:flex;"><span>./main.go:8:6: call of foo copies lock value: sync.RWMutex
</span></span><span style="display:flex;"><span>./main.go:13:12: foo passes lock by value: sync.RWMutex
</span></span></code></pre></div><p>解决上面问题可以使用指针传递:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">foo</span>(<span style="color:#a6e22e">l</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">sync</span>.<span style="color:#a6e22e">RWMutex</span>) {
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">l</span>.<span style="color:#a6e22e">Unlock</span>()
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h3 id="不可重入导致死锁">
不可重入导致死锁
<a class="anchor" href="#%e4%b8%8d%e5%8f%af%e9%87%8d%e5%85%a5%e5%af%bc%e8%87%b4%e6%ad%bb%e9%94%81">#</a>
</h3>
<p>可重入锁(ReentrantLock)指的一个线程中可以多次获取同一把锁换到Go语言场景就是一个Goroutine中Mutex和RWMutex可以连续Lock操作而不会导致死锁。同互斥体Mutex一样RWMutex也是不可重入锁不支持重入。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">l</span> <span style="color:#a6e22e">sync</span>.<span style="color:#a6e22e">RWMutex</span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">l</span>.<span style="color:#a6e22e">Lock</span>()
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">foo</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">l</span>) <span style="color:#75715e">// foo中尝试重入锁会导致死锁
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">l</span>.<span style="color:#a6e22e">Unlock</span>()
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">foo</span>(<span style="color:#a6e22e">l</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">sync</span>.<span style="color:#a6e22e">RWMutex</span>) {
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">l</span>.<span style="color:#a6e22e">Lock</span>()
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">l</span>.<span style="color:#a6e22e">Unlock</span>()
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>下面是读锁和写锁重入时候导致的死锁:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">l</span> <span style="color:#a6e22e">sync</span>.<span style="color:#a6e22e">RWMutex</span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">l</span>.<span style="color:#a6e22e">RLock</span>()
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">foo</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">l</span>)
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">l</span>.<span style="color:#a6e22e">RUnlock</span>()
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">foo</span>(<span style="color:#a6e22e">l</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">sync</span>.<span style="color:#a6e22e">RWMutex</span>) {
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">l</span>.<span style="color:#a6e22e">Lock</span>()
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">l</span>.<span style="color:#a6e22e">Unlock</span>()
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>上面代码中写锁重入时候,需要读锁先释放,而读锁释放又依赖写锁,这样就形成了死循环,导致死锁。</p>
<h2 id="进一步阅读">
进一步阅读
<a class="anchor" href="#%e8%bf%9b%e4%b8%80%e6%ad%a5%e9%98%85%e8%af%bb">#</a>
</h2>
<ul>
<li>
<a href="https://en.wikipedia.org/wiki/Readers%E2%80%93writers_problem">Readerswriters problem</a></li>
<li>
<a href="https://medium.com/golangspec/sync-rwmutex-ca6c6c3208a0">sync.RWMutex: Solving readers-writers problems</a></li>
<li>
<a href="https://cloudolife.com/2020/04/18/Programming-Language/Golang-Go/Synchronization/Use-sync-Mutex-sync-RWMutex-to-lock-share-data-for-race-condition/">Use sync.Mutex, sync.RWMutex to lock share data for race condition</a></li>
</ul>
</article>
<footer class="book-footer">
<div class="flex flex-wrap justify-between">
</div>
<script>(function(){function e(e){const t=window.getSelection(),n=document.createRange();n.selectNodeContents(e),t.removeAllRanges(),t.addRange(n)}document.querySelectorAll("pre code").forEach(t=>{t.addEventListener("click",function(){if(window.getSelection().toString())return;e(t.parentElement),navigator.clipboard&&navigator.clipboard.writeText(t.parentElement.textContent)})})})()</script>
</footer>
<div class="book-comments">
<script src="https://giscus.app/client.js" data-repo="cyub/go-1.14.13"
data-repo-id="MDEwOlJlcG9zaXRvcnkzMzc2ODUyMzQ=" data-category="Announcements"
data-category-id="DIC_kwDOFCCq8s4CZ3BC"
data-mapping="pathname" data-strict="0" data-emit-metadata="0"
data-input-position="bottom" data-reactions-enabled="0"
data-lang="zh-CN" data-theme="preferred_color_scheme" crossorigin="anonymous" async>
</script>
<noscript>Please enable JavaScript to view the comments powered by giscus.</noscript>
</div>
<label for="menu-control" class="hidden book-menu-overlay"></label>
</div>
</main>
</body>
</html>