Golang sync.Cond source code analysis

The main function of cond is that after acquiring the lock, the wait() method will wait for a notification to perform the next lock release and other operations, so as to control the appropriate release of the lock and the release frequency, which is suitable for goroutine waiting and notification in a concurrent environment.

sync.Cond for Golang 1.9, same as Golang 1.10. Source code location: sync\cond.go.

structure

type Cond struct {
	noCopy noCopy  // noCopy可以嵌入到结构中,在第一次使用后不可复制,使用go vet作为检测使用

	// 根据需求初始化不同的锁,如*Mutex 和 *RWMutex
	L Locker

	notify  notifyList  // 通知列表,调用Wait()方法的goroutine会被放入list中,每次唤醒,从这里取出
	checker copyChecker // 复制检查,检查cond实例是否被复制
}

Let's take a look at the waiting queue notifyListstructure:

type notifyList struct {
	wait   uint32
	notify uint32
	lock   uintptr
	head   unsafe.Pointer
	tail   unsafe.Pointer
}

function

NewCond

Equivalent Condconstructor for initialization Cond.

The parameter is initialized for the Locker instance. When passing the parameter, it must be a reference or pointer, such as &sync.Mutex{} or new(sync.Mutex), otherwise an exception will be reported: cannot use lock (type sync.Mutex) as type sync.Locker in argument to sync.NewCond.
You can think about why it must be a pointer? If you know, you can send me a message.

func NewCond(l Locker) *Cond {
	return &Cond{L: l}
}

Wait

Wait for the calling goroutine to automatically unlock the cL and suspend execution. After resuming execution, wait for lock cL before returning. Unlike other systems, a wait cannot return unless woken by a broadcast or signal.

because c. When the wait is first resumed, L is not locked, and the caller generally cannot assume that the conditions when the wait returns are correct. Instead, the caller should wait in a loop:

func (c *Cond) Wait() {
    // 检查c是否是被复制的,如果是就panic
	c.checker.check()
	// 将当前goroutine加入等待队列
	t := runtime_notifyListAdd(&c.notify)
	// 解锁
	c.L.Unlock()
	// 等待队列中的所有的goroutine执行等待唤醒操作
	runtime_notifyListWait(&c.notify, t)
	c.L.Lock()
}

Determine whether cond is copied.

type copyChecker uintptr

func (c *copyChecker) check() {
	if uintptr(*c) != uintptr(unsafe.Pointer(c)) &&
		!atomic.CompareAndSwapUintptr((*uintptr)(c), 0, uintptr(unsafe.Pointer(c))) &&
		uintptr(*c) != uintptr(unsafe.Pointer(c)) {
		panic("sync.Cond is copied")
	}
}

Signal

To wake up a goroutine in the waiting queue is generally to wake up a goroutine in the queue arbitrarily. Why is the FIFO mode not selected? This is because the FiFO mode is not efficient, and although it is supported, it is rarely used.

func (c *Cond) Signal() {
    // 检查c是否是被复制的,如果是就panic
	c.checker.check()
	// 通知等待列表中的一个 
	runtime_notifyListNotifyOne(&c.notify)
}

Broadcast

Wake up all goroutines in the waiting queue.

func (c *Cond) Broadcast() {
    // 检查c是否是被复制的,如果是就panic
	c.checker.check()
	// 唤醒等待队列中所有的goroutine
	runtime_notifyListNotifyAll(&c.notify)
}

example

package main

import (
	"fmt"
	"sync"
	"time"
)

var locker = new(sync.Mutex)
var cond = sync.NewCond(locker)

func main() {
	for i := 0; i < 40; i++ {
		go func(x int) {
			cond.L.Lock()         //获取锁
			defer cond.L.Unlock() //释放锁
			cond.Wait()           //等待通知,阻塞当前goroutine
			fmt.Println(x)
			time.Sleep(time.Second * 1)

		}(i)
	}
	time.Sleep(time.Second * 1)
	fmt.Println("Signal...")
	cond.Signal() // 下发一个通知给已经获取锁的goroutine
	time.Sleep(time.Second * 1)
	cond.Signal() // 3秒之后 下发一个通知给已经获取锁的goroutine
	time.Sleep(time.Second * 3)
	cond.Broadcast() //3秒之后 下发广播给所有等待的goroutine
	fmt.Println("Broadcast...")
	time.Sleep(time.Second * 60)
}


Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324913542&siteId=291194637