Maison >développement back-end >Golang >Parlons de la programmation simultanée dans Go (2)

Parlons de la programmation simultanée dans Go (2)

咔咔
咔咔original
2021-07-07 16:16:551687parcourir

Parlons de la goroutine et du canal de Go

    • 1. Utilisez le canal pour attendre la fin de la tâche
      • utilisation de la synchronisation.WaitGroup
      • Code abstrait
    • 2. Utilisez select pour la planification
      • Utilisation de. timer
    • 3. Résumé

    Articles connexes recommandés : "Parlez de la programmation simultanée dans Go (1)"

    1. est toujours dans le premier article Le code écrit dans la section 2, mais un seul paragraphe est nécessaire ici.

    package mainimport (
    	"fmt"
    	"time")func createWorker(id int) chan<p style="box-sizing: border-box; margin-top: 0px; margin-bottom: 16px; font-size: 18px; color: rgb(77, 77, 77); line-height: 26px; overflow: auto hidden;"></p>
    Ici, Kaka met ici le code source original Si vous souhaitez suivre le rythme de l'article, vous pouvez le mettre dans votre éditeur et l'exploiter.

    Alors quel est le problème avec ce code ?

    Vous pouvez voir qu'un sleep est utilisé à la fin de la fonction ChannelDemo. Cette chose ne peut pas être utilisée sans discernement dans le programme.

    En parlant de ça, laissez-moi vous raconter une petite histoire. Kaka a vu un morceau de code sur Internet qui ajoutait du sommeil.

    Ensuite, un programmeur novice n'a pas compris pourquoi ce sommeil avait été ajouté, puis a demandé au chef de projet. Le chef de projet a dit qu'une fois que le patron aurait constaté que le programme était lent, il nous demanderait de l'optimiser, et chacun. l'optimisation réduirait le temps de sommeil. Laissez le patron sentir que nous faisons quelque chose de bien.

    Les débutants marqueront le code s'ils ne le comprennent pas, puis écriront un commentaire : "Le chef de projet a demandé qu'il s'exécute lentement ici, et lorsque le patron a demandé une optimisation, le code était nettement plus rapide."

    Malheureusement, cette phrase a été vue par le patron. Le patron ne connaissait pas le code, mais il connaissait quand même le texte ! Le chef de projet a donc démissionné.

    Donc, la majeure partie du sommeil est dans un état de test et n'apparaîtra certainement pas en ligne, et alors ? Il faut résoudre ce sommeil dans le code.

    那么大家在回忆一下,在这里为什么要加sleep呢?

    发送到channel的数据都是在另一个goroutine中进行并发打印的,并发打印就会出现问题,因为根本不会知道什么时候才打印完毕。

    所以说这个sleep就会为了应对这个不知道什么时候打印完的问题,给个1毫秒让进行打印。

    这种做法是非常不好的,接下来看看使用一种新的方式来解决这个问题。

    以下代码是修改完的代码。

    package mainimport (
    	"fmt")type worker struct {
    	in   chan int
    	done chan bool}func createWorker(id int) worker {
    	w := worker{
    		in:   make(chan int),
    		done: make(chan bool),
    	}
    	go doWorker(id, w.in, w.done)
    	return w}func doWorker(id int, c chan int, done chan bool) {
    	for n := range c {
    		fmt.Printf("Worker %d receive %c\n", id, n)
    		done 

    将这些代码复制到你的本地,然后再来看一下都做了什么改动。

    • Tout d'abord, pour faciliter le passage des paramètres, nous avons établi un travailleur de structure
    • et changé la méthode de travail précédente en doWorker
    • À ce stade, la valeur de retour de la méthode createWorker ne peut pas être le canal précédent, mais le Created Structure Worker
    • Ensuite, créez tous les canaux dans la méthode createWorker. Et utilisez la structure pour transmettre les paramètres à doWorker.
    • Ce qui est finalement restitué, c'est la structure.
    • La dernière étape consiste à recevoir la valeur de Workers[i] dans les deux boucles qui envoient des données dans la méthode ChannelDemo.

    Regardez les résultats d'impression

    Parlons de la programmation simultanée dans Go (2)

    Êtes-vous un peu confus ? Comment cela peut-il être dans l'ordre ? Si c'est parallèle, alors il est nécessaire d'ouvrir les 10 travailleurs. Il suffit d'imprimer. commande ok.

    Réparons ça maintenant, je ne veux pas envoyer de tâche et attendre qu'elle se termine.

    Le mieux est de tous les envoyer et d'attendre qu'ils aient tous fini avant de sortir.

    代码实现如下

    package mainimport (
    	"fmt")type worker struct {
    	in   chan int
    	done chan bool}func createWorker(id int) worker {
    	w := worker{
    		in:   make(chan int),
    		done: make(chan bool),
    	}
    	go doWorker(id, w.in, w.done)
    	return w}func doWorker(id int, c chan int, done chan bool) {
    	for n := range c {
    		fmt.Printf("Worker %d receive %c\n", id, n)
    		done 

    在这里再进行打印看一下结果,你会发现代码是有问题的。

    Parlons de la programmation simultanée dans Go (2)

    为什么将小写的字母打印出来,而打印大写字母时发生了报错呢?

    这个就要追溯到代码中了,因为我们代码本身就写的有问题。

    还是回归到本文长谈的一个问题,那就是对于所有的channel有发送数据就必须有接收数据,如果没有接收数据就会报错。

    Donc, dans le code, pouvez-vous voir que ce bloc envoie uniquement des données mais ne reçoit pas de données ?

    Parlons de la programmation simultanée dans Go (2)

    Le problème est qu'après avoir envoyé des lettres minuscules au canal, il entrera dans la méthode doWorker, puis enverra un true to done, mais la méthode pour recevoir done est à l'arrière, c'est-à-dire say Lorsque la deuxième lettre majuscule est envoyée, une boucle d'attente est envoyée.

    Résoudre ce problème est également très simple, il suffit d'envoyer simultanément.

    Parlons de la programmation simultanée dans Go (2)

    Le résultat imprimé est également correct.

    Le cas évoqué dans cet article n'apparaîtra pas dans les projets ordinaires, il n'y a donc pas lieu de s'en inquiéter.

    Le cas présenté est simplement pour que tout le monde soit plus familier avec le mécanisme du canal.

    Il existe une autre solution à cette solution, veuillez consulter le code.

    Parlons de la programmation simultanée dans Go (2)

    Restaurez le code avant, puis bouclez pour recevoir sous chaque lettre envoyée.

    Pour cette méthode d'attente multitâche, il existe une bibliothèque en cours qui peut le faire. Jetons un coup d'œil.

    Utilisation de sync.WaitGroup

    Je ne présenterai pas l'utilisation de sync.WaitGroup un par un. Jetez simplement un œil à l'implémentation du code source.

    package mainimport (
    	"fmt"
    	"sync")type worker struct {
    	in chan int
    	wg *sync.WaitGroup}func createWorker(id int, wg *sync.WaitGroup) worker {
    	w := worker{
    		in: make(chan int),
    		wg: wg,
    	}
    	go doWorker(id, w.in, wg)
    	return w}func doWorker(id int, c chan int, wg *sync.WaitGroup) {
    	for n := range c {
    		fmt.Printf("Worker %d receive %c\n", id, n)
    		wg.Done()
    	}}func channelDemo() {
    	var wg sync.WaitGroup	var workers [10]worker	for i := 0; i 

    这份源码也是非常简单的,具体修改得东西咔咔简单介绍一下。

    • 首先取消了channelDemo这个方法中关于done的channel。
    • 使用了sync.WaitGroup,并且给createWorker方法传递sync.WaitGroup
    • createWorker方法使用了 worker的结构体。
    • 所以要先修改worker结构体,将之前的done改为wg *sync.WaitGroup即可
    • 这样就可以直接用结构体的数据。
    • 接着在doWorker方法中把最后一个参数done改为wg *sync.WaitGroup
    • 将方法中的done改为wg.Done()
    • 最后一步就是回到函数channelDemo中把任务数添加进去,然后在代码最后添加一个等待即可。

    关于这块的内容先知道这么用即可,咔咔后期会慢慢的补充并且深入。

    抽象代码

    这块的代码看起来不是那么的完美的,接下来抽象一下。

    Parlons de la programmation simultanée dans Go (2)

    这块代码有没有发现有点蹩脚,接下来我们使用函数式编程进行简单的处理。

    package mainimport (
    	"fmt"
    	"sync")type worker struct {
    	in   chan int
    	done func()}func createWorker(id int, wg *sync.WaitGroup) worker {
    	w := worker{
    		in: make(chan int),
    		done: func() {
    			wg.Done()
    		},
    	}
    	go doWorker(id, w)
    	return w}func doWorker(id int, w worker) {
    	for n := range w.in {
    		fmt.Printf("Worker %d receive %c\n", id, n)
    		w.done()
    	}}func channelDemo() {
    	var wg sync.WaitGroup	var workers [10]worker	for i := 0; i 

    这块代码看不明白就先放着,写的时间长了,你就会明白其中的含义了,学习东西不要钻牛角尖。

    二、使用select进行调度

    开头先给一个问题,假设现在有俩个channel,谁来的快先收谁应该怎么做?

    package mainimport (
    	"fmt"
    	"math/rand"
    	"time")func generator() chan int {
    	out := make(chan int)
    	go func() {
    		i := 0
    		for {
    			// 随机睡眠1500毫秒以内
    			time.Sleep(
    				time.Duration(rand.Intn(1500)) *
    					time.Millisecond)
    			// 往out这个channel发送i值
    			out 

    以上就是代码实现,代码注释也写的非常的清晰明了,就不过多的做解释了。

    主要用法还是对channel的使用,在带上了一个新的概念select,可以在多个通道,那个通道先发送数据,就先执行谁,并且这个select也是可以并行执行channel管道。

    在上文写的createWorkerworker俩个方法还记得吧!接下来就不在select里边直接打印了。

    就使用之前写的俩个方法融合在一起,咔咔已将将源码写好了,接下来看一下实现。

    package mainimport (
    	"fmt"
    	"math/rand"
    	"time")func worker(id int, c chan int) {
    	for n := range c {
    		fmt.Printf("Worker %d receive %d\n", id, n)
    	}}func createWorker(id int) chan

    运行代码

    Parlons de la programmation simultanée dans Go (2)

    看到Parlons de la programmation simultanée dans Go (2)得知也是没有问题的。

    这段代码虽然运行没有任何问题,但是这样有什么缺点呢?

    Parlons de la programmation simultanée dans Go (2)

    可以看下这段代码n := 这里先收了一个值,然后在下边代码<code style="box-sizing: border-box; font-family: " source code pro sans mono menlo monaco consolas inconsolata courier monospace sc yahei sans-serif font-size: background-color: rgb border-radius: padding: line-height: color:>w 又会阻塞住,这个是不好的。

    那么希望是怎么执行的呢?

    Parlons de la programmation simultanée dans Go (2)

    这种模式是在select中既可以收数据,也可以发数据,目前这个程序是编译不过的,请看修改后的源码。

    package mainimport (
    	"fmt"
    	"math/rand"
    	"time")func worker(id int, c chan int) {
    	for n := range c {
    		fmt.Printf("Worker %d receive %d\n", id, n)
    	}}func createWorker(id int) chan

    这个模式还是有缺点的,因为n收c1和c2的速度跟消耗的速度是不一样的。

    假设c1的生成速度特别快,一下子生成了1,2,3。那么最后输出的数据有可能就只有3,而1和2就无法输出了。

    这个场景也是非常好模拟的,只需要在打印的位置加上一点延迟时间即可。

    Parlons de la programmation simultanée dans Go (2)

    此时你会看到Parlons de la programmation simultanée dans Go (2)为0、7、12、20…中间很多的数字都没来得急打印。

    因此我们就需要把收到的n存下来进行排队输出。

    package mainimport (
    	"fmt"
    	"math/rand"
    	"time")func worker(id int, c chan int) {
    	for n := range c {
    		// 手动让消耗速度变慢
    		time.Sleep(5 * time.Second)
    		fmt.Printf("Worker %d receive %d\n", id, n)
    	}}func createWorker(id int) chan 0 {
    			activeWorker = worker			// 取出索引为0的值
    			activeValue = values[0]
    		}
    		/**
    		select 方式进行调度
    		        使用场景:比如有多个通道,但我打算是哪一个通道先给我数据,我就先执行谁
    		        这个select 可以是并行执行 channel管道
    		*/
    		select {
    		case n := 

    以上就是实现代码

    此时在来看Parlons de la programmation simultanée dans Go (2)。

    Parlons de la programmation simultanée dans Go (2)

    Parlons de la programmation simultanée dans Go (2)没有漏掉数据,并且也是无序的,这样就非常好了。

    计时器的使用

    上面的这个程序是退出不了的,我们想让它10s后就直接退出怎么做呢?

    那就需要使用计时器来进行操作了。

    package mainimport (
    	"fmt"
    	"math/rand"
    	"time")func worker(id int, c chan int) {
    	for n := range c {
    		// 手动让消耗速度变慢
    		time.Sleep(time.Second)
    		fmt.Printf("Worker %d receive %d\n", id, n)
    	}}func createWorker(id int) chan 0 {
    			activeWorker = worker			// 取出索引为0的值
    			activeValue = values[0]
    		}
    		/**
    		select 方式进行调度
    		        使用场景:比如有多个通道,但我打算是哪一个通道先给我数据,我就先执行谁
    		        这个select 可以是并行执行 channel管道
    		*/
    		select {
    		case n := 

    这里就是源码的实现,可以看到直接在select中是可以收到tm的值的,也就说如果到了10s,就会执行打印bye的操作。

    Alors maintenant, il y a une autre exigence, c'est-à-dire que si les données ne sont pas reçues dans les 800 millisecondes, d'autres choses peuvent être faites.

    En utilisant l'idée de​​tirer des conclusions à partir d'un exemple, vous pouvez réfléchir à la façon de procéder.

    Parlons de la programmation simultanée dans Go (2)

    C'est en fait très simple, il vous suffit de régler une minuterie dans le boîtier.

    Maintenant que j'ai mentionné cela, permettez-moi d'ajouter un autre usage pour vousParlons de la programmation simultanée dans Go (2) := time.Tick(time.Second)

    , qui est également utilisé dans les cas.

    Parlons de la programmation simultanée dans Go (2)

    这样就可以每秒来显示一下values队列有多少数据。

    这块的内容就结束了,最终给大家发一下源码,感兴趣的可以在自己的编辑器上试试看。

    package mainimport (
    	"fmt"
    	"math/rand"
    	"time")func worker(id int, c chan int) {
    	for n := range c {
    		// 手动让消耗速度变慢
    		time.Sleep(time.Second)
    		fmt.Printf("Worker %d receive %d\n", id, n)
    	}}func createWorker(id int) chan 0 {
    			activeWorker = worker			// 取出索引为0的值
    			activeValue = values[0]
    		}
    		/**
    		select 方式进行调度
    		        使用场景:比如有多个通道,但我打算是哪一个通道先给我数据,我就先执行谁
    		        这个select 可以是并行执行 channel管道
    		*/
    		select {
    		case n := 

    三、总结

    本文主要就是对于goroutine和channel的大量练习。

    文中的案例,有可能会一时半会理解不了,是没有关系的,不用钻牛角尖的。

    Lorsque vous nagez longtemps dans l'océan, certaines choses deviendront naturellement claires pour vous.

    Le prochain article est de vous présenter un projet de robot d'exploration simultané pratique.

    La persévérance dans l'apprentissage, la persévérance dans l'écriture et la persévérance dans le partage sont les convictions auxquelles Kaka a toujours adhéré depuis ses débuts. J'espère que les articles de Kaka sur le grand Internet pourront vous apporter un peu d'aide. Je m'appelle Kaka, à la prochaine fois.

    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:
    Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn