>백엔드 개발 >Golang >Golang의 select 구현 메커니즘

Golang의 select 구현 메커니즘

藏色散人
藏色散人앞으로
2020-09-11 13:17:463316검색

다음 칼럼에서는 golang tutorial 칼럼에서 Golang의 select 구현 메커니즘을 자세히 설명하겠습니다. 필요한 친구들에게 도움이 되길 바랍니다!

Golang의 select 구현 메커니즘

Text

오늘 셀렉트 플레이를 하다가 문제를 발견했습니다.

클립 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)
		}
	}
}

클립 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 질문:
1. 위 스니펫의 출력은 무엇입니까?
2. 어떻게 설명할까요?

첫 번째 문제는 실행하기만 하면 확실히 출력 결과가 달라집니다.
Clip 1:

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

Clip 2:

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

두 번째는 select가 두 번 채널을 모니터링해서 번갈아가며 나타나기 때문에 이해하기 쉽습니다.
그럼 첫 번째에는 왜 하나만 등장하는 걸까요?
이 문제를 해결하기 위해 select의 구현 메커니즘을 수정해야 해서 이 글을 쓰게 되었습니다.

select 메커니즘에 대한 간략한 설명

Select에는 주의가 필요한 여러 메커니즘이 있습니다
1.select+case는 고루틴을 차단하고 모니터링하는 데 사용됩니다. 사례가 없으면 select{}만 현재 프로그램에서 goroutine을 모니터링합니다. . , 이때 실제 goroutine이 실행되어야 한다는 점에 유의하세요. 그렇지 않으면 select{}가 패닉을 보고합니다

2. select 아래에 실행 가능한 사례가 여러 개 있는 경우 하나가 무작위로 실행됩니다.

3.Select는 종종 for 루프와 함께 작동하여 채널에서 일어나는 이야기가 있는지 모니터링합니다. 이 시나리오에서는 break는 현재 선택을 종료하기만 하고 종료하지는 않는다는 점에 유의해야 합니다. break TIP/goto를 사용해야 합니다.

4. 버퍼링이 없는 채널이 값을 전달한 후 즉시 닫히면 닫히기 전에 차단됩니다. 채널에 버퍼링이 있는 경우 여러 고루틴이 켜져 있어도 계속해서 후속 값을 받습니다. 동일한 채널을 닫으면 복구 패닉 방법을 사용하여 채널 폐쇄 문제를 확인할 수 있습니다

위 지식 포인트를 읽은 후에도 이 기사의 핵심 의심을 설명할 수 없으므로 계속하세요!

select 메커니즘에 대한 자세한 설명

select 메커니즘은 /src/runtime/select.go에서 확인할 수 있습니다.

소스 코드 조각 해석:

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
}

표시의 편의를 위해 특별히 프로세스를 설명하기 위해 보기 흉한 그림을 만들었습니다.

Golang의 select 구현 메커니즘즉, 선택은 4단계로 수행됩니다.

이 글에서 의심되는 핵심 포인트는 해당 루프에서 실행 파일이 발견되면

이 선택에서 실행되지 않는 경우에 해당하는 채널이 팀의 현재 고루틴에 주어지며,

, time.Tick은 조각 2처럼 전역 스택에 있는 대신 그 자리에서 생성되므로 하나가 실행될 때마다 다른 하나는 버려지고 다시 시작되어야 합니다. - 다시 선택하면 새로운 것을 얻으려면 처음부터 다시 시작해야 합니다. 이것은 일시적인 이해입니다. 더 나은 이해가 있으시면 메시지를 남겨주세요. 감사합니다.

위 내용은 Golang의 select 구현 메커니즘의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 csdn.net에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제