Golang中的CAS原子操作 和 锁

在高并发编程中,经常会出现对同一个资源并发访问修改的情况,为了保证最终结果的正确性,一般会使用 CAS原子操作 来实现。

如要对一个变量进行计数统计,两种实现方式分别为

package main

import (
	"fmt"
	"sync"
)

// 锁实现方式
func main() {
	var count int64
	var wg sync.WaitGroup
	var mu sync.Mutex

	for i := 0; i < 10000; i++ {
		wg.Add(1)
		go func(wg *sync.WaitGroup) {
			defer wg.Done()
			mu.Lock()
			count = count + 1
			mu.Unlock()
		}(&wg)
	}
	wg.Wait()

	// count = 10000
	fmt.Println("count = ", count)
}

package main

import (
	"fmt"
	"sync"

	"sync/atomic"
)

// atomic CAS 原子操作
func main() {
	var count int64
	var wg sync.WaitGroup

	for i := 0; i < 10000; i++ {
		wg.Add(1)
		go func(wg *sync.WaitGroup) {
			defer wg.Done()
			// 失败一直重试
			for {
				old := atomic.LoadInt64(&count)
				if atomic.CompareAndSwapInt64(&count, old, old+1) {
					break
				}
			}

		}(&wg)
	}
	wg.Wait()

	// count = 10000
	fmt.Println("count = ", count)
}

可以看到两种用法的执行结果是一样的,我们再看一下两者的性能区别。

Continue reading

Runtime: Golang同步原语Mutex源码分析

sync 包下提供了最基本的同步原语,如互斥锁 Mutex。除 OnceWaitGroup 类型外,大部分是由低级库提供的,更高级别的同步最好是通过 channel 通讯来实现。

Mutex 类型的变量默认值是未加锁状态,在第一次使用后,此值将不得复制,这点切记!!!

本文基于go version: 1.16.2

Mutex 锁实现了 Locker 接口。

// A Locker represents an object that can be locked and unlocked.
type Locker interface {
	Lock()
	Unlock()
}

锁的模式

为了互斥公平性,Mutex 分为 正常模式饥饿模式 两种。

正常模式

在正常模式下,等待者 waiter 会进入到一个FIFO队列,在获取锁时waiter会按照先进先出的顺序获取。当唤醒一个waiter 时它被并不会立即获取锁,而是要与新来的goroutine竞争,这种情况下新来的goroutine比较有优势,主要是因为它已经运行在CPU,可能它的数量还不少,所以waiter大概率下获取不到锁。在这种waiter获取不到锁的情况下,waiter会被添加到队列的前面。如果waiter获取不到锁的时间超出了1毫秒,它将被切换为饥饿模式。

这里的 waiter 是指新来一个goroutine 时会尝试一次获取锁,如果获取不到我们就视其为watier,并将其添加到FIFO队列里。

饥饿模式

在正常模式下,每次新来的goroutine都会抢走锁,就这会导致一些 waiter 永远也获取不到锁,产生饥饿问题。所以为了应对高并发抢锁场景下的公平性,官方引入了饥饿模式。

在饥饿模式下,锁将直接交给队列最前面的waiter。新来的goroutine即使在锁未被持有情况下也不会参与竞争锁,同时也不会进行自旋,而直接将其添加到队列的尾部。

如果拥有锁的waiter发现有以下两种情况,它将切换回正常模式:

  1. 它是队列里的最后一个waiter,再也没有其它waiter
  2. 等待时间小于1毫秒

模式区别

正常模式 拥有更好的性能,因为即使等待队列里有抢锁的 waiter,由于新来的goroutine 正在CPU中运行,所以优先获取到锁。
饥饿模式 是对公平性和性能的一种平衡,它避免了某些 goroutine 长时间的等待锁。在饥饿模式下,优先处理的是那些一直在等待的 waiter。饥饿模式在一定机时会切换回正常模式。

Continue reading