Maison  >  Article  >  développement back-end  >  Le mécanisme de mise en œuvre de select dans Golang

Le mécanisme de mise en œuvre de select dans Golang

藏色散人
藏色散人avant
2020-09-11 13:17:463256parcourir

Ce qui suit est une explication détaillée du mécanisme de mise en œuvre de select dans Golang à partir de la colonne tutoriel golang J'espère que cela sera utile aux amis dans le besoin !

Le mécanisme de mise en œuvre de select dans Golang

Texte

Je joue aujourd'hui J'ai trouvé un problème lors de la sélection, comme suit :

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

Deux questions :
1. dehors?
2. Comment expliquer ?

Le premier problème est facile à résoudre. Il suffit de l'exécuter et il est évident que les résultats de sortie sont définitivement différents.
Fragment 1 :

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

Fragment 2 :

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

Le second est facile à comprendre, car select surveille les chaînes de deux fois, donc elles apparaissent alternativement.
Alors pourquoi le premier n'apparaît-il qu'une seule fois ?
Afin de résoudre ce problème, j'ai dû réviser le mécanisme d'implémentation de select, j'ai donc rédigé cet article.

Brève description du mécanisme de sélection

Select a plusieurs mécanismes qui nécessitent une attention
1.select+case est utilisé pour bloquer la goroutine d'écoute S'il n'y a pas de cas, juste une sélection. { }, il s'agit de surveiller la goroutine dans le programme actuel. À ce stade, veuillez noter qu'il doit y avoir une vraie goroutine en cours d'exécution, sinon select{} signalera une panique

S'il y a plusieurs exécutables. Dans les cas sélectionnés, l'exécution sera aléatoire.

3.Select coopère souvent avec la boucle for pour surveiller s'il y a une histoire qui se passe sur la chaîne. Il convient de noter que dans ce scénario, break ne quitte que la sélection actuelle et ne sort pas pour. Vous devez utiliser break TIP / goto.

4. Si un canal sans mise en mémoire tampon se ferme immédiatement après avoir transmis la valeur, il se bloquera avant de se fermer. Si un canal a une mise en mémoire tampon, il continuera à recevoir les valeurs suivantes même s'il est fermé.

5. Plusieurs goroutines dans le même canal peuvent être fermées. Vous pouvez utiliser la méthode de panique de récupération pour déterminer le problème de fermeture du canal

Après avoir lu les points de connaissances ci-dessus, je ne peux toujours pas expliquer les principaux doutes. de cet article, alors continuez !

Explication détaillée du mécanisme de sélection

Vous pouvez consulter /src/runtime/select.go pour en savoir plus sur le mécanisme de sélection.

Interprétation des extraits de code source :

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
}

Pour faciliter l'affichage, j'ai spécialement réalisé une image moche pour illustrer le processus :

Le mécanisme de mise en œuvre de select dans Golang

Autrement dit, la sélection s'effectue en quatre étapes.

Le point clé de doute dans cet article est que dans cette boucle, lorsqu'un exécutable est trouvé,

Les canaux correspondant aux cas qui ne seront pas exécutés dans cette sélection donneront la goroutine actuelle du team. Si vous les ignorez, vous perdrez . Puisque time.Tick est créé sur place dans le cas, au lieu d'être dans la pile globale comme le fragment 2, chaque fois que l'un est exécuté, l'autre est abandonné. , lorsque vous le sélectionnez à nouveau, vous devez le récupérer, il est nouveau et vous devez recommencer.

Voici ma compréhension actuelle. Si vous avez une meilleure compréhension, veuillez me laisser un message, merci.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer