Skip to content

Latest commit

 

History

History
210 lines (160 loc) · 5.12 KB

同步.md

File metadata and controls

210 lines (160 loc) · 5.12 KB

互斥锁

互斥锁 sync.Mutex 是传统并发程序对共享资源进行访问控制的主要手段。

var m sync.Mutex
func TestMutex(t *testing.T) {
	m.Lock()
	defer m.Unlock()
	//TODO:执行一些操作
}

对一个未锁定的互斥锁进行解锁操作时,会导致程序异常结束,即使采用 defer 也没法拦截。

func main() {
	defer func() {
		if err := recover(); err != nil {
			fmt.Printf("Recovered the panic: %#v\n", err)
		}
	}()
	mu.Lock()
	fmt.Println("Locked")
	mu.Unlock()
	fmt.Println("Unlocked")
	mu.Unlock()
	fmt.Println("Unlocked")
}

读写锁

sync.RWMutex 与互斥锁最大的不同,就是可以分别针对读操作和写操作进行锁定和解锁操作。读操作和写操作互斥,但多个读操作不互斥。

var rwm sync.RWMutex

//读锁
rwm.RLock()
rwm.RUnlock()

//写锁
rwm.Lock()
rwm.Unlock()

条件变量

  1. sync.NewCond(l Locker): 新建一个 sync.Cond 变量。该函数需要一个 Locker 作为必填参数,这是因为在 cond.Wait() 中底层会涉及到 Locker 的锁操作。
  2. cond.Wait(): 等待被唤醒并且自动地对与该条件变量关联的那个锁进行解锁。唤醒期间会使它所在的 goroutine 阻塞。
  3. cond.Signal(): 只唤醒一个最先 Waitgoroutine。对应的另外一个唤醒函数是 Broadcast,区别是 Signal 一次只会唤醒一个 goroutine,而 Broadcast 会将全部 Waitgoroutine 都唤醒。

用法

常见的场景时:生产者消费者场景。而 producerconsumerqueue 的读写操作都由 sync.Mutex 进行并发安全的保护。

var mutex sync.Mutex
var cond = sync.NewCond(&mutex)
var queue []int

func producer() {
	i := 0
	for {
		mutex.Lock()
		queue = append(queue, i)
		i++
		cond.Signal()
		mutex.Unlock()
	}
}

func consumer(consumerName string) {
	for {
		mutex.Lock()
		for len(queue) == 0 {
			cond.Wait() //等待被唤醒。唤醒期间会解锁并阻塞 goroutine。
		}
		fmt.Println(consumerName, "got", queue[0])
		queue = queue[1:]
		mutex.Unlock()
	}
}

func TestCond(t *testing.T) {
	go producer()

	go consumer("consumer1")
	go consumer("consumer2")

	for {
		time.Sleep(time.Second * 1)
	}
}

注意事项

  1. sync.Cond不能拷贝。

  2. Wait 的调用一定要放在 LockUnLock 中间,Wait 调用的条件检查一定要放在 for 循环中。

    c.L.Lock()
    for !condition() {
        c.Wait()
    }
    ... make use of condition ...
    c.L.Unlock()

原子操作

  1. 原子操作即执行过程不能被中断的操作。在针对某个值的原子操作执行过程当中,CPU 绝不会再去执行其他针对该值的操作。
  2. 由标准库sync/atomic 来提供对几种简单类型的值执行原子操作。这些类型包括:int32int64uint32uint64uintptrunsafe.Pointer
func TestYz(t *testing.T) {
	var x int32
	var wg sync.WaitGroup
	for i := 0; i < 5000; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			atomic.AddInt32(&x, 1)
		}()
	}
	wg.Wait()
	fmt.Println(x)
}

增、减

//有符号的增减
var x int32
atomic.AddInt32(&x, 10)
atomic.AddInt32(&x, -3)

//无符号的增减
var y uint32
atomic.AddUint32(&y, 10)
atomic.AddUint32(&y, ^uint32(10-1))

比较并交换

  1. func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool) 第一个参数的值是指向被操作值的指针值,该值的类型为*int32 。后面两个参数的类型都是 int32 。调用该函数时会先判断 addr 指向的值与参数 old 的值是否相等。仅当此判断得到肯定的结果之后,该函数才会用参数 new 代表的新值替换旧值。
  2. 与前面讲到的锁相比,CAS 操作明显不同。它总是假设被操作值未曾改变(即与旧值相等),并一旦确定这个假设的真实性就立即进行替换。而锁则是更加谨慎的做法。锁总是先假设会有并发的操作修改被操作值,并需要使用锁将相关操作放入临界区中加以保护。可以说,使用锁的做法趋于悲观,而 CAS 操作的做法趋于乐观。
//eg:1
  var z int32 = 2
  atomic.CompareAndSwapInt32(&z, 2, 4)
  fmt.Println(z) //4

//增加一个值
func addValue(delta int32) {
	for {
		v := atomic.LoadInt32(&value) //载入(读取)操作
		if atomic.CompareAndSwapInt32(&value, v, v+delta) {
			break
		}
	}
}

存储

Store 为前缀的函数可以原子地存储某个值。

交换

Swap 为前缀的函数可以执行原子交换操作,不关心被操作值的旧值。

原子值

atomic.Value 是一个结构体类型,它用于存储需要原子读写的值。该值一旦声明不允许复制。

var atomicVal atomic.Value

Store 方法的限制

  1. 作为参数传入该方法的值不能为nil。
  2. 作为参数传入该方法的值必须与之前传入的值类型相同。

只执行一次

使用场景:执行仅需执行一次的任务。

var once sync.Once
var x int32
for i := 0; i < 100; i++ {
  once.Do(func() {
    x++
  })
}

参考《 Go 并发编程实战》第5章同步