Heim  >  Artikel  >  Backend-Entwicklung  >  Lassen Sie uns über die gleichzeitige Programmierung in Go (2) sprechen.

Lassen Sie uns über die gleichzeitige Programmierung in Go (2) sprechen.

咔咔
咔咔Original
2021-07-07 16:16:551522Durchsuche

Lassen Sie uns über Gos Goroutine und Channel sprechen

    • 1. Verwenden Sie den Kanal, um auf das Ende der Aufgabe zu warten Timer
      • 3. Zusammenfassung
      Empfohlene verwandte Artikel: „
    • Sprechen Sie über gleichzeitige Programmierung in Go (1)
      1. Verwenden Sie Kanäle, um auf das Ende der Aufgabe zu warten

    Der Anwendungsfall Im ersten Artikel befindet sich noch der in Abschnitt 2 geschriebene Code, hier ist jedoch nur ein Absatz erforderlich.

    package mainimport (
    	"fmt"
    	"time")func createWorker(id int) chan

    Hier stellt Kaka den Original-Quellcode ein. Wenn Sie dem Rhythmus des Artikels folgen möchten, können Sie ihn in Ihren Editor einfügen und bedienen.

    Was ist dann das Problem mit diesem Code?

    Sie können sehen, dass am Ende des Kanals ein Schlaf verwendet wirdDemo-Funktion Dieses Ding kann im Programm nicht wahllos verwendet werden.

    Apropos, lass mich dir eine kleine Geschichte erzählen, die im Internet einen Code gesehen hat, der für Schlaf sorgt.

    Dann verstand ein unerfahrener Programmierer nicht, warum dieser Schlaf hinzugefügt wurde, und fragte dann den Projektmanager. Der Projektmanager sagte, dass er uns bitten würde, es zu optimieren, nachdem er festgestellt hatte, dass das Programm langsam sei Eine Optimierung würde die Schlafzeit verkürzen. Geben Sie dem Chef das Gefühl, dass wir etwas Gutes tun.

    Neulinge markieren den Code, wenn sie ihn nicht verstehen, und schreiben dann einen Kommentar: „Der Projektmanager hat hier nach langsamer Ausführung gefragt, und als der Chef nach Optimierung gefragt hat, war der Code deutlich schneller.“

    Leider wurde dieser Satz vom Chef gesehen. Der Chef kannte den Code nicht, aber er kannte trotzdem den Text! Also trat der Projektmanager zurück.

    Der Großteil von Sleep befindet sich also in einem Teststadium und wird definitiv nicht online erscheinen, na und? Es ist notwendig, diesen Schlaf im Code zu lösen.

    那么大家在回忆一下,在这里为什么要加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 

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

    • Zur Vereinfachung der Parameterübergabe haben wir zunächst einen Struktur-Worker eingerichtet
    • und die vorherige Worker-Methode in doWorker geändert
    • Zu diesem Zeitpunkt kann der Rückgabewert der createWorker-Methode nicht der vorherige Kanal sein, sondern der erstellter Strukturarbeiter
    • Dann erstellen Sie alle Kanäle in der Methode „createWorker“. Und verwenden Sie die Struktur, um Parameter an doWorker zu übergeben.
    • Was letztendlich zurückgegeben wird, ist die Struktur.
    • Der letzte Schritt besteht darin, den Wert von Workern[i] in den beiden Schleifen zu empfangen, die Daten in der Methode „channelDemo“ senden.

    Schauen Sie sich die Druckergebnisse an

    Lassen Sie uns über die gleichzeitige Programmierung in Go (2) sprechen.

    Sind Sie etwas verwirrt? Wenn es parallel ist, müssen Sie einfach die 10 Arbeiter öffnen Ordnung.

    Lassen Sie uns dieses Problem jetzt lösen. Ich möchte keine Aufgabe senden und warten, bis sie beendet ist.

    Das Beste ist, sie alle rauszuschicken und zu warten, bis sie alle fertig sind, bevor Sie gehen.

    代码实现如下

    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 

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

    Lassen Sie uns über die gleichzeitige Programmierung in Go (2) sprechen.

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

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

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

    Können Sie im Code sehen, dass dieser Block nur Daten sendet, aber keine Daten empfängt?

    Lassen Sie uns über die gleichzeitige Programmierung in Go (2) sprechen.

    Das Problem besteht darin, dass nach dem Senden von Kleinbuchstaben an den Kanal die DoWorker-Methode eingegeben wird und dann ein True an „Done“ gesendet wird, aber die Methode zum Empfangen von „Done“ befindet sich hinten, d. h. an sagen: Wenn der zweite Großbuchstabe gesendet wird, wird eine Warteschleife gesendet.

    Die Lösung dieses Problems ist ebenfalls sehr einfach. Wir müssen nur gleichzeitig fertige Dateien senden.

    Lassen Sie uns über die gleichzeitige Programmierung in Go (2) sprechen.

    Das gedruckte Ergebnis ist auch korrekt.

    Der in diesem Artikel beschriebene Fall wird in normalen Projekten nicht vorkommen, sodass Sie sich darüber keine Sorgen machen müssen.

    Der angegebene Fall dient nur dazu, alle mit dem Kanalmechanismus vertrauter zu machen.

    Es gibt eine andere Lösung für diese Lösung, siehe Code.

    Lassen Sie uns über die gleichzeitige Programmierung in Go (2) sprechen.

    Stellen Sie den Code auf den vorherigen Stand wieder her und wiederholen Sie dann die Schleife, um unter jedem gesendeten Brief fertig zu werden.

    Für diese Multitasking-Wartemethode gibt es eine Bibliothek, die dies tun kann.

    Verwendung von sync.WaitGroup

    Ich werde die Verwendung von sync.WaitGroup nicht einzeln vorstellen. Schauen Sie sich einfach die Quellcode-Implementierung an.

    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中把任务数添加进去,然后在代码最后添加一个等待即可。

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

    抽象代码

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

    Lassen Sie uns über die gleichzeitige Programmierung in Go (2) sprechen.

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

    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

    运行代码

    Lassen Sie uns über die gleichzeitige Programmierung in Go (2) sprechen.

    看到Lassen Sie uns über die gleichzeitige Programmierung in Go (2) sprechen.得知也是没有问题的。

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

    Lassen Sie uns über die gleichzeitige Programmierung in Go (2) sprechen.

    可以看下这段代码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 又会阻塞住,这个是不好的。

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

    Lassen Sie uns über die gleichzeitige Programmierung in Go (2) sprechen.

    这种模式是在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就无法输出了。

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

    Lassen Sie uns über die gleichzeitige Programmierung in Go (2) sprechen.

    此时你会看到Lassen Sie uns über die gleichzeitige Programmierung in Go (2) sprechen.为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 := 

    以上就是实现代码

    此时在来看Lassen Sie uns über die gleichzeitige Programmierung in Go (2) sprechen.。

    Lassen Sie uns über die gleichzeitige Programmierung in Go (2) sprechen.

    Lassen Sie uns über die gleichzeitige Programmierung in Go (2) sprechen.没有漏掉数据,并且也是无序的,这样就非常好了。

    计时器的使用

    上面的这个程序是退出不了的,我们想让它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的操作。

    Jetzt gibt es also eine weitere Anforderung, das heißt, wenn die Daten nicht innerhalb von 800 Millisekunden empfangen werden, können andere Dinge getan werden.

    Anhand der Idee, aus einem Beispiel Schlussfolgerungen zu ziehen, können Sie darüber nachdenken, wie das geht.

    Lassen Sie uns über die gleichzeitige Programmierung in Go (2) sprechen.

    Es ist eigentlich ganz einfach, Sie müssen nur einen Timer im Gehäuse einstellen.

    Nachdem ich dies erwähnt habe, möchte ich für Sie eine weitere Verwendung hinzufügenLassen Sie uns über die gleichzeitige Programmierung in Go (2) sprechen. := time.Tick(time.Second)

    , die auch in Fällen verwendet wird.

    Lassen Sie uns über die gleichzeitige Programmierung in Go (2) sprechen.

    这样就可以每秒来显示一下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的大量练习。

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

    Wenn man längere Zeit im Meer schwimmt, werden einem einige Dinge ganz natürlich klar.

    Der nächste Artikel soll Ihnen ein praktisches, paralleles Crawler-Projekt vorstellen.

    Beharrlichkeit beim Lernen, Beharrlichkeit beim Schreiben und Beharrlichkeit beim Teilen sind die Überzeugungen, an denen Kaka seit seinen Anfängen festgehalten hat. Ich hoffe, dass Kakas Artikel im riesigen Internet Ihnen ein wenig helfen können. Ich bin Kaka, bis zum nächsten Mal.

    Das obige ist der detaillierte Inhalt vonLassen Sie uns über die gleichzeitige Programmierung in Go (2) sprechen.. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

    Stellungnahme:
    Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn