Go TryLock实现
<h1 id="go-trylock实现">Go TryLock实现</h1><p>Go标准库的<code>sync/Mutex</code>、<code>RWMutex</code>实现了<code>sync/Locker</code>接口, 提供了<code>Lock()</code>和<code>UnLock()</code>方法,可以获取锁和释放锁,我们可以方便的使用它来控制我们对共享资源的并发控制上。</p>
<p>但是标准库中的<code>Mutex.Lock</code>的锁被获取后,如果在未释放之前再调用<code>Lock</code>则会被阻塞住,这种设计在有些情况下可能不能满足我的需求。有时候我们想尝试获取锁,如果获取到了,没问题继续执行,如果获取不到,我们不想阻塞住,而是去调用其它的逻辑,这个时候我们就想要<code>TryLock</code>方法了。</p>
<h3 id="使用-unsafe-操作指针">使用 <code>unsafe</code> 操作指针</h3>
<p>如果你查看<code>sync/Mutex</code>的代码,会发现<code>Mutext</code>的数据结构如下所示:</p>
<pre><code class="language-go">type Mutex struct {
state int32
semauint32
}
</code></pre>
<p>它使用<code>state</code>这个32位的整数来标记锁的占用,所以我们可以使用<code>CAS</code>来尝试获取锁。</p>
<p>代码实现如下:</p>
<pre><code class="language-go">const mutexLocked = 1 << iota
type Mutex struct {
sync.Mutex
}
func (m *Mutex) TryLock() bool {
return atomic.CompareAndSwapInt32((*int32)(unsafe.Pointer(&m.Mutex)), 0, mutexLocked)
}
</code></pre>
<p>使用起来和标准库的<code>Mutex</code>用法一样。</p>
<pre><code class="language-go">func main() {
var m Mutex
m.Lock()
go func() {
m.Lock()
}()
time.Sleep(time.Second)
fmt.Printf("TryLock: %t\n", m.TryLock()) //false
fmt.Printf("TryLock: %t\n", m.TryLock()) // false
m.Unlock()
fmt.Printf("TryLock: %t\n", m.TryLock()) //true
fmt.Printf("TryLock: %t\n", m.TryLock()) //false
m.Unlock()
fmt.Printf("TryLock: %t\n", m.TryLock()) //true
m.Unlock()
}
</code></pre>
<p>注意<code>TryLock</code>不是检查锁的状态,而是<strong>尝试获取</strong>锁,所以<code>TryLock</code>返回true的时候事实上这个锁已经被获取了。</p>
<h3 id="实现自旋锁">实现自旋锁</h3>
<p>上面一节给了我们启发,利用 <code>uint32</code>和<code>CAS</code>操作我们可以一个自定义的锁:</p>
<pre><code class="language-go">type SpinLock struct {
f uint32
}
func (sl *SpinLock) Lock() {
for !sl.TryLock() {
runtime.Gosched()
}
}
func (sl *SpinLock) Unlock() {
atomic.StoreUint32(&sl.f, 0)
}
func (sl *SpinLock) TryLock() bool {
return atomic.CompareAndSwapUint32(&sl.f, 0, 1)
}
</code></pre>
<p>整体来看,它好像是标准库的一个精简版,没有休眠和唤醒的功能。</p>
<p>当然这个自旋锁可以在大并发的情况下CPU的占用率可能比较高,这是因为它的<code>Lock</code>方法使用了自旋的方式,如果别人没有释放锁,这个循环会一直执行,速度可能更快但CPU占用率高。</p>
<p>当然这个版本还可以进一步的优化,尤其是在复制的时候。下面是一个优化的版本:</p>
<pre><code class="language-go">type spinLock uint32
func (sl *spinLock) Lock() {
for !atomic.CompareAndSwapUint32((*uint32)(sl), 0, 1) {
runtime.Gosched() //without this it locks up on GOMAXPROCS > 1
}
}
func (sl *spinLock) Unlock() {
atomic.StoreUint32((*uint32)(sl), 0)
}
func (sl *spinLock) TryLock() bool {
return atomic.CompareAndSwapUint32((*uint32)(sl), 0, 1)
}
func SpinLock() sync.Locker {
var lock spinLock
return &lock
}
</code></pre>
<h3 id="使用-channel-实现">使用 channel 实现</h3>
<p>另一种方式是使用channel:</p>
<pre><code class="language-go">type ChanMutex chan struct{}
func (m *ChanMutex) Lock() {
ch := (chan struct{})(*m)
ch <- struct{}{}
}
func (m *ChanMutex) Unlock() {
ch := (chan struct{})(*m)
select {
case <-ch:
default:
panic("unlock of unlocked mutex")
}
}
func (m *ChanMutex) TryLock() bool {
ch := (chan struct{})(*m)
select {
case ch <- struct{}{}:
return true
default:
}
return false
}
</code></pre>
<p>有兴趣的同学可以关注我的同事写的库 lrita/gosync。</p>
<h3 id="性能比较">性能比较</h3>
<p>首先看看上面三种方式和标准库中的<code>Mutex</code>、<code>RWMutex</code>的<code>Lock</code>和<code>Unlock</code>的性能比较:</p>
<pre><code>BenchmarkMutex_LockUnlock-4 100000000 16.8 ns/op 0 B/op 0 allocs/op
BenchmarkRWMutex_LockUnlock-4 50000000 36.8 ns/op 0 B/op 0 allocs/op
BenchmarkUnsafeMutex_LockUnlock-4 100000000 16.8 ns/op 0 B/op 0 allocs/op
BenchmarkChannMutex_LockUnlock-4 20000000 65.6 ns/op 0 B/op 0 allocs/op
BenchmarkSpinLock_LockUnlock-4 100000000 18.6 ns/op 0 B/op 0 allocs/op
</code></pre>
<p>可以看到单线程(goroutine)的情况下 <code>spinlock</code>并没有比标准库好多少,反而差一点,并发测试的情况比较好,如下表中显示,这是符合预期的。</p>
<p><code>unsafe</code>方式和标准库差不多。</p>
<p><code>channel</code>方式的性能就比较差了。</p>
<pre><code>BenchmarkMutex_LockUnlock_C-4 20000000 75.3 ns/op 0 B/op 0 allocs/op
BenchmarkRWMutex_LockUnlock_C-4 20000000 100 ns/op 0 B/op 0 allocs/op
BenchmarkUnsafeMutex_LockUnlock_C-4 20000000 75.3 ns/op 0 B/op 0 allocs/op
BenchmarkChannMutex_LockUnlock_C-4 10000000 231 ns/op 0 B/op 0 allocs/op
BenchmarkSpinLock_LockUnlock_C-4 50000000 32.3 ns/op 0 B/op 0 allocs/op
</code></pre>
<p>再看看三种实现<code>TryLock</code>方法的锁的性能:</p>
<pre><code>BenchmarkUnsafeMutex_Trylock-4 50000000 34.0 ns/op 0 B/op 0 allocs/op
BenchmarkChannMutex_Trylock-4 20000000 83.8 ns/op 0 B/op 0 allocs/op
BenchmarkSpinLock_Trylock-4 50000000 30.9 ns/op 0 B/op 0 allocs/op
</code></pre>
</div>
<div id="MySignature" role="contentinfo">
Songzhibin<br><br>
来源:https://www.cnblogs.com/binHome/p/14149750.html
頁:
[1]