Der Anwendungsfall Im ersten Artikel befindet sich noch der in Abschnitt 2 geschriebene Code, hier ist jedoch nur ein Absatz erforderlich.
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.
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
Nach dem Login kopieren
Nach dem Login kopieren
在这里再进行打印看一下结果,你会发现代码是有问题的。
为什么将小写的字母打印出来,而打印大写字母时发生了报错呢?
这个就要追溯到代码中了,因为我们代码本身就写的有问题。
还是回归到本文长谈的一个问题,那就是对于所有的channel有发送数据就必须有接收数据,如果没有接收数据就会报错。
Können Sie im Code sehen, dass dieser Block nur Daten sendet, aber keine Daten empfängt?
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.
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.
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
Nach dem Login kopieren
这份源码也是非常简单的,具体修改得东西咔咔简单介绍一下。
- 首先取消了
channelDemo
这个方法中关于done的channel。
- 使用了
sync.WaitGroup
,并且给createWorker方法传递sync.WaitGroup
- createWorker方法使用了 worker的结构体。
- 所以要先修改worker结构体,将之前的done改为wg *sync.WaitGroup即可
- 这样就可以直接用结构体的数据。
- 接着在doWorker方法中把最后一个参数done改为wg *sync.WaitGroup
- 将方法中的done改为wg.Done()
- 最后一步就是回到函数channelDemo中把任务数添加进去,然后在代码最后添加一个等待即可。
关于这块的内容先知道这么用即可,咔咔后期会慢慢的补充并且深入。
这块的代码看起来不是那么的完美的,接下来抽象一下。
这块代码有没有发现有点蹩脚,接下来我们使用函数式编程进行简单的处理。
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
Nach dem Login kopieren
这块代码看不明白就先放着,写的时间长了,你就会明白其中的含义了,学习东西不要钻牛角尖。
开头先给一个问题,假设现在有俩个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
Nach dem Login kopieren
以上就是代码实现,代码注释也写的非常的清晰明了,就不过多的做解释了。
主要用法还是对channel的使用,在带上了一个新的概念select,可以在多个通道,那个通道先发送数据,就先执行谁,并且这个select也是可以并行执行channel管道。
在上文写的createWorker
和worker
俩个方法还记得吧!接下来就不在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
Nach dem Login kopieren
Nach dem Login kopieren
看到Lassen Sie uns über die gleichzeitige Programmierung in Go (2) sprechen.得知也是没有问题的。
这段代码虽然运行没有任何问题,但是这样有什么缺点呢?
可以看下这段代码n := 这里先收了一个值,然后在下边代码w 又会阻塞住,这个是不好的。
这种模式是在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
Nach dem Login kopieren
Nach dem Login kopieren
这个模式还是有缺点的,因为n收c1和c2的速度跟消耗的速度是不一样的。
假设c1的生成速度特别快,一下子生成了1,2,3。那么最后输出的数据有可能就只有3,而1和2就无法输出了。
这个场景也是非常好模拟的,只需要在打印的位置加上一点延迟时间即可。
此时你会看到Lassen Sie uns über die gleichzeitige Programmierung in Go (2) sprechen.为0、7、12、20…中间很多的数字都没来得急打印。
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 :=
Nach dem Login kopieren
此时在来看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 :=
Nach dem Login kopieren
Nach dem Login kopieren
这里就是源码的实现,可以看到直接在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.
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.
这样就可以每秒来显示一下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 :=
Nach dem Login kopieren
Nach dem Login kopieren
本文主要就是对于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.