<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Test on GYM</title><link>https://oooooomy.cloud/tags/test/</link><description>Recent content in Test on GYM</description><generator>Hugo</generator><language>en-us</language><copyright>© ccdgr</copyright><lastBuildDate>Thu, 11 Apr 2024 00:00:00 +0800</lastBuildDate><atom:link href="https://oooooomy.cloud/tags/test/index.xml" rel="self" type="application/rss+xml"/><item><title>Go 并发安全 Map 三种实现对比：从互斥锁到分段锁</title><link>https://oooooomy.cloud/posts/go/map/</link><pubDate>Thu, 11 Apr 2024 00:00:00 +0800</pubDate><guid>https://oooooomy.cloud/posts/go/map/</guid><description>&lt;blockquote&gt;
&lt;p&gt;实验环境：Go 1.24, Apple M4, macOS 15, 10 核 CPU&lt;/p&gt;
&lt;p&gt;所有测试均通过 &lt;code&gt;go test -race&lt;/code&gt; 竞态检测，零数据竞争。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="目录"&gt;目录&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#%E9%97%AE%E9%A2%98%E8%83%8C%E6%99%AF"&gt;问题背景&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#%E8%A7%A3%E6%B3%95%E4%B8%80syncrwmutex"&gt;解法一：sync.RWMutex&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#%E8%A7%A3%E6%B3%95%E4%BA%8Csyncmap"&gt;解法二：sync.Map&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#%E8%A7%A3%E6%B3%95%E4%B8%89%E5%88%86%E6%AE%B5%E9%94%81-shardmap"&gt;解法三：分段锁 ShardMap&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#%E6%B5%8B%E8%AF%95%E7%AD%96%E7%95%A5%E4%B8%8E%E7%94%A8%E4%BE%8B"&gt;测试策略与用例&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#benchmark-%E6%80%A7%E8%83%BD%E5%AF%B9%E6%AF%94"&gt;Benchmark 性能对比&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#%E5%88%86%E7%89%87%E5%9D%87%E5%8C%80%E6%80%A7%E9%AA%8C%E8%AF%81"&gt;分片均匀性验证&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#%E7%AB%9E%E6%80%81%E6%A3%80%E6%B5%8B"&gt;竞态检测&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#%E5%A6%82%E4%BD%95%E9%80%89%E6%8B%A9"&gt;如何选择&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#%E5%AE%8C%E6%95%B4%E6%BA%90%E7%A0%81"&gt;完整源码&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="问题背景"&gt;问题背景&lt;/h2&gt;
&lt;p&gt;Go 原生的 &lt;code&gt;map&lt;/code&gt; &lt;strong&gt;不是并发安全的&lt;/strong&gt;。多个 goroutine 同时读写同一个 map 会触发 race condition，轻则数据错乱，重则 &lt;code&gt;fatal error: concurrent map read and map write&lt;/code&gt; 直接崩溃。&lt;/p&gt;
&lt;p&gt;Go 中实现并发安全 map 有三种主流方案，各有优劣。我把三种都写了一遍，用同一套测试跑了一下，记录一下结果。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="解法一syncrwmutex"&gt;解法一：sync.RWMutex&lt;/h2&gt;
&lt;h3 id="思路"&gt;思路&lt;/h3&gt;
&lt;p&gt;最传统、最稳妥的做法。用一个 &lt;code&gt;sync.RWMutex&lt;/code&gt; 保护整个 map：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;读操作&lt;/strong&gt;：加 &lt;code&gt;RLock()&lt;/code&gt;（读锁之间不互斥）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;写操作&lt;/strong&gt;：加 &lt;code&gt;Lock()&lt;/code&gt;（独占）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="实现"&gt;实现&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#8be9fd;font-style:italic"&gt;type&lt;/span&gt; RWMutexMap &lt;span style="color:#8be9fd;font-style:italic"&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; mu sync.RWMutex
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; data &lt;span style="color:#8be9fd;font-style:italic"&gt;map&lt;/span&gt;[&lt;span style="color:#8be9fd"&gt;string&lt;/span&gt;]&lt;span style="color:#8be9fd"&gt;int&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#8be9fd;font-style:italic"&gt;func&lt;/span&gt; &lt;span style="color:#50fa7b"&gt;NewRWMutexMap&lt;/span&gt;() &lt;span style="color:#ff79c6"&gt;*&lt;/span&gt;RWMutexMap {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff79c6"&gt;return&lt;/span&gt; &lt;span style="color:#ff79c6"&gt;&amp;amp;&lt;/span&gt;RWMutexMap{data: &lt;span style="color:#8be9fd;font-style:italic"&gt;make&lt;/span&gt;(&lt;span style="color:#8be9fd;font-style:italic"&gt;map&lt;/span&gt;[&lt;span style="color:#8be9fd"&gt;string&lt;/span&gt;]&lt;span style="color:#8be9fd"&gt;int&lt;/span&gt;)}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#8be9fd;font-style:italic"&gt;func&lt;/span&gt; (m &lt;span style="color:#ff79c6"&gt;*&lt;/span&gt;RWMutexMap) &lt;span style="color:#50fa7b"&gt;Get&lt;/span&gt;(key &lt;span style="color:#8be9fd"&gt;string&lt;/span&gt;) (&lt;span style="color:#8be9fd"&gt;int&lt;/span&gt;, &lt;span style="color:#8be9fd"&gt;bool&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; m.mu.&lt;span style="color:#50fa7b"&gt;RLock&lt;/span&gt;() &lt;span style="color:#6272a4"&gt;// 读锁：允许多个 goroutine 同时持有&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff79c6"&gt;defer&lt;/span&gt; m.mu.&lt;span style="color:#50fa7b"&gt;RUnlock&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; v, ok &lt;span style="color:#ff79c6"&gt;:=&lt;/span&gt; m.data[key]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff79c6"&gt;return&lt;/span&gt; v, ok
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#8be9fd;font-style:italic"&gt;func&lt;/span&gt; (m &lt;span style="color:#ff79c6"&gt;*&lt;/span&gt;RWMutexMap) &lt;span style="color:#50fa7b"&gt;Set&lt;/span&gt;(key &lt;span style="color:#8be9fd"&gt;string&lt;/span&gt;, val &lt;span style="color:#8be9fd"&gt;int&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; m.mu.&lt;span style="color:#50fa7b"&gt;Lock&lt;/span&gt;() &lt;span style="color:#6272a4"&gt;// 写锁：独占&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff79c6"&gt;defer&lt;/span&gt; m.mu.&lt;span style="color:#50fa7b"&gt;Unlock&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; m.data[key] = val
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#8be9fd;font-style:italic"&gt;func&lt;/span&gt; (m &lt;span style="color:#ff79c6"&gt;*&lt;/span&gt;RWMutexMap) &lt;span style="color:#50fa7b"&gt;Delete&lt;/span&gt;(key &lt;span style="color:#8be9fd"&gt;string&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; m.mu.&lt;span style="color:#50fa7b"&gt;Lock&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff79c6"&gt;defer&lt;/span&gt; m.mu.&lt;span style="color:#50fa7b"&gt;Unlock&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#8be9fd;font-style:italic"&gt;delete&lt;/span&gt;(m.data, key)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="优点"&gt;优点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;实现简单，代码量最小&lt;/li&gt;
&lt;li&gt;逻辑直观，容易理解和维护&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RWMutex&lt;/code&gt; 在读多写少场景下性能可接受&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="缺点"&gt;缺点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;只有一把全局锁，所有写操作串行化&lt;/li&gt;
&lt;li&gt;高并发写入时，锁竞争成为瓶颈&lt;/li&gt;
&lt;li&gt;读写锁本身有开销&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="解法二syncmap"&gt;解法二：sync.Map&lt;/h2&gt;
&lt;h3 id="思路-1"&gt;思路&lt;/h3&gt;
&lt;p&gt;Go 标准库 &lt;code&gt;sync&lt;/code&gt; 包内置的并发安全 map。接口是 &lt;code&gt;any&lt;/code&gt; 类型的（无泛型），我们封装一层提供类型安全。&lt;/p&gt;</description></item></channel></rss>