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

Golang의 select 구현 메커니즘

藏色散人
풀어 주다: 2020-09-11 13:17:46
앞으로
3319명이 탐색했습니다.

다음 칼럼에서는 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으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿