Home >Backend Development >Golang >The implementation mechanism of select in Golang

The implementation mechanism of select in Golang

藏色散人
藏色散人forward
2020-09-11 13:17:463315browse

The following column golang tutorial will give you a detailed explanation of the implementation mechanism of select in Golang. I hope it will be helpful to friends in need!

The implementation mechanism of select in Golang

#Text

Speaking of playing today A problem was found when selecting, as follows:

Fragment 1:

func main(){
	var count int
	for {
		select {
		case <-time.Tick(time.Millisecond * 500):
			fmt.Println("咖啡色的羊驼")
			count++
			fmt.Println("count--->" , count)
		case <-time.Tick(time.Millisecond * 499) :
			fmt.Println(time.Now().Unix())
			count++
			fmt.Println("count--->" , count)
		}
	}
}

Fragment 2:

func main(){
	t1 := time.Tick(time.Second)
	t2 := time.Tick(time.Second)
	var count int
	for {
		select {
		case <-t1:
			fmt.Println("咖啡色的羊驼")
			count++
			fmt.Println("count--->" , count)
		case <-t2 :
			fmt.Println(time.Now().Unix())
			count++
			fmt.Println("count--->" , count)
		}
	}
}

Two questions:

1. The output of the above fragment turn out?
2.How to explain?

The first problem is easy to solve. Just run it. It is obvious that the output results are definitely different.

Fragment 1:

1535673600
count---> 1
1535673600
count---> 2
1535673601
count---> 3

Fragment 2:

咖啡色的羊驼
count---> 1
1535673600
count---> 2
咖啡色的羊驼
count---> 3
1535673601
count---> 4

The second one is easy to understand, because select monitors the two time channels, so they appear alternately.

So why only 1 appears in the first one?
In order to solve this problem, we have to revise the implementation mechanism of select, so we have this article.

Select mechanism brief description

select has several mechanisms that need attention

1.select case is used to block the listening goroutine. If there is no case, just a select{} , it is to monitor the goroutine in the current program. At this time, please note that there needs to be a real goroutine running, otherwise select{} will report panic

2. If there are multiple executable cases under select, they will be executed randomly. one.

3.Select often cooperates with the for loop to monitor whether there is a story happening on the channel. It should be noted that in this scenario, break only exits the current selection and does not exit for. You need to use break TIP / goto.

4. If the channel without buffering closes immediately after passing the value, it will block before closing. If the channel has buffering, it will continue to receive subsequent values ​​even if it is closed.

5 . Multiple goroutines in the same channel are closed. You can use the recover panic method to determine the channel closure problem.

After reading the above knowledge points, I still cannot explain the core doubts of this article, so continue!

Detailed explanation of the select mechanism

You can view /src/runtime/select.go to learn about the select mechanism.

Interpretation of source code snippets:

func selectgo(sel *hselect) int {
	// ...

	// case洗牌
	pollslice := slice{unsafe.Pointer(sel.pollorder), int(sel.ncase), int(sel.ncase)}
	pollorder := *(*[]uint16)(unsafe.Pointer(&pollslice))
	for i := 1; i < int(sel.ncase); i++ {
		//....
	}

	// 给case排序
	lockslice := slice{unsafe.Pointer(sel.lockorder), int(sel.ncase), int(sel.ncase)}
	lockorder := *(*[]uint16)(unsafe.Pointer(&lockslice))
	for i := 0; i < int(sel.ncase); i++ {
		// ...
	}
	for i := int(sel.ncase) - 1; i >= 0; i-- {
		// ...
	}

	// 加锁该select中所有的channel
	sellock(scases, lockorder)

	// 进入loop
loop:
	// ... 
	// pass 1 - look for something already waiting
	// 按顺序遍历case来寻找可执行的case
	for i := 0; i < int(sel.ncase); i++ {
		//...
		switch cas.kind {
		case caseNil:
			continue
		case caseRecv:
			// ... goto xxx
		case caseSend:
			// ... goto xxx
		case caseDefault:
			dfli = casi
			dfl = cas
		}
	}

	// 没有找到可以执行的case,但有default条件,这个if里就会直接退出了。
	if dfl != nil {
		// ...
	}
	// ...

	// pass 2 - enqueue on all chans
	// chan入等待队列
	for _, casei := range lockorder {
		// ...
		switch cas.kind {
		case caseRecv:
			c.recvq.enqueue(sg)

		case caseSend:
			c.sendq.enqueue(sg)
		}
	}

	// wait for someone to wake us up
	// 等待被唤起,同时解锁channel(selparkcommit这里实现的)
	gp.param = nil
	gopark(selparkcommit, nil, "select", traceEvGoBlockSelect, 1)
	
	// 突然有故事发生,被唤醒,再次该select下全部channel加锁
	sellock(scases, lockorder)

	// pass 3 - dequeue from unsuccessful chans
	// 本轮最后一次循环操作,获取可执行case,其余全部出队列丢弃
	casi = -1
	cas = nil
	sglist = gp.waiting
	// Clear all elem before unlinking from gp.waiting.
	for sg1 := gp.waiting; sg1 != nil; sg1 = sg1.waitlink {
		sg1.isSelect = false
		sg1.elem = nil
		sg1.c = nil
	}
	gp.waiting = nil

	for _, casei := range lockorder {
		// ...
		if sg == sglist {
			// sg has already been dequeued by the G that woke us up.
			casi = int(casei)
			cas = k
		} else {
			c = k.c
			if k.kind == caseSend {
				c.sendq.dequeueSudoG(sglist)
			} else {
				c.recvq.dequeueSudoG(sglist)
			}
		}
		// ...
	}

	// 没有的话,再走一次loop
	if cas == nil {
		goto loop
	}
	// ...
bufrecv:
	// can receive from buffer
bufsend:
	// ...
recv:
	// ...
rclose:
	// ...
send:
	// ...
retc:
	// ...
sclose:
	// send on closed channel
}

In order to facilitate the display, I specially made an ugly picture to illustrate the process:

The implementation mechanism of select in Golang

In other words, selection is carried out in four steps.

The key point of doubt in this article is that in that loop, when an executable is found,

The channels corresponding to the cases that will not be executed in this selection will give the current goroutine of the team. Regardless of them, is lost. Since time.Tick is created on-site in the case, rather than in the global stack like fragment 2, every time any one is executed, the other one is abandoned. , when you select it again, you need to get it again, and it is new and you need to start over.

This is my current understanding. If you have a better understanding, please leave me a message. Thank you.

The above is the detailed content of The implementation mechanism of select in Golang. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:csdn.net. If there is any infringement, please contact admin@php.cn delete