并发编程 on 深入Go语言之旅 https://go.cyub.vip/concurrency/ Recent content in 并发编程 on 深入Go语言之旅 Hugo -- gohugo.io zh-cn 一次性操作 - sync.Once https://go.cyub.vip/concurrency/sync-once/ Mon, 01 Jan 0001 00:00:00 +0000 https://go.cyub.vip/concurrency/sync-once/ 一次性操作 - sync.Once # sync.Once用来完成一次性操作,比如配置加载,单例对象初始化等。 源码分析 # sync.Once定义如下: type Once struct { done uint32 // 用来标志操作是否操作 m Mutex // 锁,用来第一操作时候,加锁处理 } 接下来看剩下的全部代码: func (o *Once) Do(f func()) { if atomic.LoadUint32(&amp;o.done) == 0 {// 原子性加载o.done,若值为1,说明已完成操作,若为0,说明未完成操作 o.doSlow(f) } } func (o *Once) doSlow(f func()) { o.m.Lock() // 加锁 defer o.m.Unlock() if o.done == 0 { // 再次进行o.done是否等于0判断,因为存在并发调用doSlow的情况 defer atomic.StoreUint32(&amp;o.done, 1) // 将o.done值设置为1,用来标志操作完成 f() // 执行操作 } } 上下文 - context https://go.cyub.vip/concurrency/context/ Mon, 01 Jan 0001 00:00:00 +0000 https://go.cyub.vip/concurrency/context/ <h1 id="上下文---context"> 上下文 - context <a class="anchor" href="#%e4%b8%8a%e4%b8%8b%e6%96%87---context">#</a> </h1> <p>Context是由Golang官方开发的并发控制包,一方面可以用于当请求超时或者取消时候,相关的goroutine马上退出释放资源,另一方面Context本身含义就是上下文,其可以在多个goroutine或者多个处理函数之间传递共享的信息。</p> <p>创建一个新的context,必须基于一个父context,新的context又可以作为其他context的父context。所有context在一起构造成一个context树。</p> <p> <img src="https://static.cyub.vip/images/202008/context-tree.jpg" alt="context tree" /></p> 互斥锁 - sync.Mutex https://go.cyub.vip/concurrency/sync-mutex/ Mon, 01 Jan 0001 00:00:00 +0000 https://go.cyub.vip/concurrency/sync-mutex/ 互斥锁 - sync.Mutex # 内存模型 - memroy model https://go.cyub.vip/concurrency/memory-model/ Mon, 01 Jan 0001 00:00:00 +0000 https://go.cyub.vip/concurrency/memory-model/ 内存模型 # Go语言中的内存模型规定了多个goroutine读取变量时候,变量的可见性情况。注意本章节的内存模型并不是内存对象分配、管理、回收的模型,准确的说这里面的内存模型是内存一致性模型。 Happens Before原则 # Happens Before原则的定义是如果一个操作e1先于操作e2发生,那么我们就说e1 happens before e2,也可以描述成e2 happens after e2,此时e1操作的变量结果对e2都是可见的。如果e1操作既不先于e2发生又不晚于e2发生,我们说e1操作与e2操作并发发生。 Happens Before具有传导性:如果操作e1 happens before 操作e2,e3 happends before e1,那么e3一定也 happends before e2。 由于存在指令重排和多核CPU并发访问情况,我们代码中变量顺序和实际方法顺序并不总是一致的。考虑下面一种情况: a := 1 b := 2 c := a + 1 上面代码中是先给变量a赋值,然后给变量b赋值,最后给编程c赋值。但是在底层实现指令时候,可能发生指令重排:变量b赋值在前,变量a赋值在后,最后变量c赋值。对于依赖于a变量的c变量的赋值,不管怎样指令重排,Go语言都会保证变量a赋值操作 happends before c变量赋值操作。 上面代码运行是运行在同一goroutine中,Go语言时能够保证happends before原则的,实现正确的变量可见性。但对于多个goroutine共享数据时候,Go语言是无法保证Happens Before原则的,这时候就需要我们采用锁、通道等同步手段来保证数据一致性。考虑下面场景: var a, b int // goroutine A go func() { a = 1 b = 2 }() // goroutine B go func() { if b == 2 { print(a) } }() 当执行goroutine B打印变量a时并不一定打印出来1,有可能打印出来的是0。这是因为goroutine A中可能存在指令重排,先将b变量赋值2,若这时候接着执行goroutine B那么就会打印出来0 原子操作-atomic https://go.cyub.vip/concurrency/atomic/ Mon, 01 Jan 0001 00:00:00 +0000 https://go.cyub.vip/concurrency/atomic/ 原子操作 - atomic # atomic是Go内置原子操作包。下面是官方说明: Package atomic provides low-level atomic memory primitives useful for implementing synchronization algorithms. atomic包提供了用于实现同步机制的底层原子内存原语。 These functions require great care to be used correctly. Except for special, low-level applications, synchronization is better done with channels or the facilities of the sync package. Share memory by communicating; don&rsquo;t communicate by sharing memory. 使用这些功能需要非常小心。除了特殊的底层应用程序外,最好使用通道或sync包来进行同步。通过通信来共享内存;不要通过共享内存来通信。 atomic包提供的操作可以分为三类: 对整数类型T的操作 # T类型是int32、int64、uint32、uint64、uintptr其中一种。 func AddT(addr *T, delta T) (new T) func CompareAndSwapT(addr *T, old, new T) (swapped bool) func LoadT(addr *T) (val T) func StoreT(addr *T, val T) func SwapT(addr *T, new T) (old T) 对于unsafe. 并发Map - sync.Map https://go.cyub.vip/concurrency/sync-map/ Mon, 01 Jan 0001 00:00:00 +0000 https://go.cyub.vip/concurrency/sync-map/ 并发Map - sync.Map # 源码分析 # sync.Map的结构: type Map struct { mu Mutex // 排他锁,用于对dirty map操作时候加锁处理 read atomic.Value // read map // dirty map。新增key时候,只写入dirty map中,需要使用mu dirty map[interface{}]*entry // 用来记录从read map中读取key时miss的次数 misses int } sync.Map结构体中read字段是atomic.Value类型,底层是readOnly结构体: type readOnly struct { m map[interface{}]*entry amended bool // 当amended为true时候,表示sync.Map中的key也存在dirty map中 } read map和dirty map的value类型是*entry, entry结构体定义如下: // expunged用来标记从dirty map删除掉了 var expunged = unsafe.Pointer(new(interface{})) type entry struct { // 如果p == nil 说明对应的entry已经被删除掉了, 且m.dirty == nil // 如果 p == expunged 说明对应的entry已经被删除了,但m. 条件变量 - sync.Cond https://go.cyub.vip/concurrency/sync-cond/ Mon, 01 Jan 0001 00:00:00 +0000 https://go.cyub.vip/concurrency/sync-cond/ 条件变量 - sync.Cond # 等待组 - sync.WaitGroup https://go.cyub.vip/concurrency/sync-waitgroup/ Mon, 01 Jan 0001 00:00:00 +0000 https://go.cyub.vip/concurrency/sync-waitgroup/ 等待组 - sync.WaitGroup # 源码分析 # type WaitGroup struct { noCopy noCopy // waitgroup是不能够拷贝复制的,是通过go vet来检测实现 /* waitgroup使用一个int64来计数:高32位,用来add计数,低32位用来记录waiter数量。 若要原子性更新int64就必须保证该int64对齐系数是8,即64位对齐。 对于64位系统,直接使用一个int64类型字段就能保证原子性要求,但对32位系统就不行了。 所以实现的时候并没有直接一个int64, 而是使用[3]int32数组,若[0]int32地址恰好是8对齐的,那就waitgroup int64 = [0]int32 + [1]int32, 否则一定是4对齐的, 故[0]int32不用,恰好错开了4字节,此时[1]int32一定是8对齐的。此时waitgroup int64 = [1]int32 + [2]int32 通过这个技巧恰好满足32位和64位系统下int64都能原子性操作 */ state1 [3]uint32 // waitgroup对齐系数是4 } func (wg *WaitGroup) state() (statep *uint64, semap *uint32) { // 当state1是8对齐的,则返回低8字节(statep)用来计数,即state1[0]是add计数,state1[1]是waiter计数 if uintptr(unsafe.Pointer(&amp;wg.state1))%8 == 0 { return (*uint64)(unsafe.Pointer(&amp;wg.state1)), &amp;wg.state1[2] } else { // 反之,则返回高8字节用来计数,即state1[1]是add计数,state1[2]是waiter计数 return (*uint64)(unsafe.Pointer(&amp;wg.state1[1])), &amp;wg.state1[0] } } // Add方法用来更新add计数器。即将原来计数值加上delta,delta可以为负值 // waitgroup的Done方法本质上就是Add(-1) // Add更新之后的计数器值不能小于0。当计数器值等于0时候,会释放信号,所有调用Wait方法而阻塞的Goroutine不再阻塞(释放的信号量=waiter计数) func (wg *WaitGroup) Add(delta int) { statep, semap := wg. 缓冲池 - sync.Pool https://go.cyub.vip/concurrency/sync-pool/ Mon, 01 Jan 0001 00:00:00 +0000 https://go.cyub.vip/concurrency/sync-pool/ 缓冲池 - sync.Pool # A Pool is a set of temporary objects that may be individually saved and retrieved. Any item stored in the Pool may be removed automatically at any time without notification. If the Pool holds the only reference when this happens, the item might be deallocated. A Pool is safe for use by multiple goroutines simultaneously. Pool&rsquo;s purpose is to cache allocated but unused items for later reuse, relieving pressure on the garbage collector. 读写锁 - sync.RWMutex https://go.cyub.vip/concurrency/sync-rwmutex/ Mon, 01 Jan 0001 00:00:00 +0000 https://go.cyub.vip/concurrency/sync-rwmutex/ 读写锁 - sync.RWMutex # RWMutex是Go语言中内置的一个reader/writer锁,用来解决读者-写者问题(Readers–writers problem)。在任意一时刻,一个RWMutex只能由任意数量的reader持有,或者只能由一个writer持有。 读者-写者问题 # 读者-写者问题(Readers–writers 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. 通道 - channel https://go.cyub.vip/concurrency/channel/ Mon, 01 Jan 0001 00:00:00 +0000 https://go.cyub.vip/concurrency/channel/ <h1 id="通道---channel"> 通道 - channel <a class="anchor" href="#%e9%80%9a%e9%81%93---channel">#</a> </h1> <p>Golang中Channel是goroutine间重要通信的方式,是并发安全的,通道内的数据First In First Out,我们可以把通道想象成队列。</p> <h2 id="channel数据结构"> channel数据结构 <a class="anchor" href="#channel%e6%95%b0%e6%8d%ae%e7%bb%93%e6%9e%84">#</a> </h2> <p>Channel底层数据结构是一个结构体。</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">hchan</span> <span style="color:#66d9ef">struct</span> { </span></span><span style="display:flex;"><span> <span style="color:#a6e22e">qcount</span> <span style="color:#66d9ef">uint</span> <span style="color:#75715e">// 队列中元素个数 </span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">dataqsiz</span> <span style="color:#66d9ef">uint</span> <span style="color:#75715e">// 循环队列的大小 </span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">buf</span> <span style="color:#a6e22e">unsafe</span>.<span style="color:#a6e22e">Pointer</span> <span style="color:#75715e">// 指向循环队列 </span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">elemsize</span> <span style="color:#66d9ef">uint16</span> <span style="color:#75715e">// 通道里面的元素大小 </span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">closed</span> <span style="color:#66d9ef">uint32</span> <span style="color:#75715e">// 通道关闭的标志 </span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">elemtype</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">_type</span> <span style="color:#75715e">// 通道元素的类型 </span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">sendx</span> <span style="color:#66d9ef">uint</span> <span style="color:#75715e">// 待发送的索引,即循环队列中的队尾指针rear </span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">recvx</span> <span style="color:#66d9ef">uint</span> <span style="color:#75715e">// 待读取的索引,即循环队列中的队头指针front </span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">recvq</span> <span style="color:#a6e22e">waitq</span> <span style="color:#75715e">// 接收等待队列 </span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">sendq</span> <span style="color:#a6e22e">waitq</span> <span style="color:#75715e">// 发送等待队列 </span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">lock</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></span></code></pre></div>