>  기사  >  백엔드 개발  >  Go의 동시 프로그래밍에 대해 이야기해 보겠습니다. (2)

Go의 동시 프로그래밍에 대해 이야기해 보겠습니다. (2)

咔咔
咔咔원래의
2021-07-07 16:16:551647검색

Go의 고루틴과 채널에 대해 이야기해 봅시다

    • 1. 채널을 사용하여 작업이 끝날 때까지 기다립니다.
      • sync.WaitGroup 사용법
      • 추상 코드
    • 2. 타이머
      • 3. 요약
    • 추천 관련 기사: "
    Go의 동시 프로그래밍에 대해 이야기하기(1)

    "1. 작업이 끝날 때까지 기다리기 위한 채널 사용

    섹션 2에 작성된 코드는 여전히 첫 번째 기사에 있지만 여기서는 한 단락만 필요합니다.

    package mainimport (
    	"fmt"
    	"time")func createWorker(id int) chan
    여기 Kaka가 원본 소스 코드를 여기에 넣습니다. 기사의 리듬을 따르고 싶다면 편집기에 넣고 작동하면 됩니다.

    그렇다면 이 코드의 문제점은 무엇인가요?

    channelDemo 함수 마지막에 sleep을 사용하는 것을 볼 수 있는데, 이는 프로그램 내에서 무분별하게 사용할 수 없습니다.

    그러고보니, 잠깐 이야기를 하자면 카카는 인터넷에서 잠을 추가하는 코드를 보았습니다.

    그런데 초보 프로그래머가 왜 이 수면이 추가되었는지 이해하지 못하고 프로젝트 관리자에게 물었습니다. 프로젝트 관리자는 상사가 프로그램이 느리다는 것을 알게 된 후 우리에게 최적화를 요청하겠다고 말했습니다. 최적화하면 수면 시간이 단축됩니다. 상사가 우리가 좋은 일을 하고 있다고 느끼게 해주세요.

    새내기들은 코드를 이해하지 못하면 표시를 하고 "여기서 프로젝트 관리자가 느린 실행을 요청했고, 상사가 최적화를 요청했을 때 코드가 훨씬 빨라졌습니다."라고 댓글을 남깁니다.

    안타깝게도 이 문장을 사장님이 봤어요. 사장님은 코드는 몰랐지만, 텍스트는 알고 계셨어요! 그래서 프로젝트 매니저는 물러났습니다.

    그래서 대부분의 수면은 테스트 상태이고 확실히 온라인에 나타나지 않을 텐데, 그럼 어쩌죠? 이 수면을 코드에서 해결해야 합니다.

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

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

    • 우선 매개변수 전달의 편의를 위해 구조체 워커
    • 를 구축하고 이전 워커 메소드를 doWorker
    • 로 변경했습니다. 이때 createWorker 메소드의 반환값은 이전 채널이 될 수 없지만, Created Structure Worker
    • 그런 다음 createWorker 메서드에서 모든 채널을 생성합니다. 그리고 이 구조를 사용하여 doWorker에 매개변수를 전달합니다.
    • 드디어 반환되는 것은 구조입니다.
    • 마지막 단계는 채널데모 메소드로 데이터를 보내는 두 개의 루프에서 Workers[i]의 값을 받는 것입니다.

    인쇄 결과를 보세요

    Go의 동시 프로그래밍에 대해 이야기해 보겠습니다. (2)

    이게 좀 헷갈리시나요? 병렬이라면 10인을 열어야겠죠? 알았어.

    이제 이 문제를 해결해 보겠습니다. 작업을 보내고 끝날 때까지 기다리고 싶지 않습니다.

    가장 좋은 방법은 모두 보내고 종료하기 전에 모두 끝날 때까지 기다리는 것입니다.

    代码实现如下

    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 

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

    Go의 동시 프로그래밍에 대해 이야기해 보겠습니다. (2)

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

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

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

    그렇다면 코드에서 해당 블록은 데이터를 보내기만 하고 데이터를 받지는 않는 것을 볼 수 있나요?

    Go의 동시 프로그래밍에 대해 이야기해 보겠습니다. (2)

    문제는 채널에 소문자를 보낸 후 doWorker 메서드에 들어간 다음 true를 done으로 보내는데 done을 받는 메서드가 뒤에 있다는 것, 즉 say 두 번째 대문자가 전송되면 대기 루프가 전송됩니다.

    이 문제를 해결하는 것도 매우 간단합니다. 동시에 완료를 보내기만 하면 됩니다.

    Go의 동시 프로그래밍에 대해 이야기해 보겠습니다. (2)

    인쇄된 결과를 보는 것도 정확합니다.

    이 글에서 제시한 사례는 일반 프로젝트에서는 나타나지 않으므로 걱정할 필요가 없습니다.

    주어진 사례는 모든 사람이 채널 메커니즘에 더 익숙해지도록 하기 위한 것입니다.

    이 솔루션에 대한 또 다른 솔루션이 있습니다. 코드를 참조하세요.

    Go의 동시 프로그래밍에 대해 이야기해 보겠습니다. (2)

    코드를 이전으로 복원한 다음 보낸 각 문자 아래에서 완료되도록 루프를 반복합니다.

    이 다중 작업 대기 방법에 대해 이를 수행할 수 있는 라이브러리가 있습니다.

    sync.WaitGroup 사용법

    sync.WaitGroup 사용법은 하나씩 소개하지 않겠습니다. 소스 코드 구현만 살펴보세요.

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

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

    抽象代码

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

    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

    运行代码

    Go의 동시 프로그래밍에 대해 이야기해 보겠습니다. (2)

    看到Go의 동시 프로그래밍에 대해 이야기해 보겠습니다. (2)得知也是没有问题的。

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

    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 又会阻塞住,这个是不好的。

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

    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就无法输出了。

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

    Go의 동시 프로그래밍에 대해 이야기해 보겠습니다. (2)

    此时你会看到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 := 

    以上就是实现代码

    此时在来看Go의 동시 프로그래밍에 대해 이야기해 보겠습니다. (2)。

    Go의 동시 프로그래밍에 대해 이야기해 보겠습니다. (2)

    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的操作。

    이제 또 다른 요구 사항이 있습니다. 즉, 800밀리초 이내에 데이터가 수신되지 않으면 다른 작업을 수행할 수 있다는 것입니다.

    한 예에서 추론을 이끌어낸다는 아이디어를 활용하면 이를 어떻게 수행할지 생각해 볼 수 있습니다.

    Go의 동시 프로그래밍에 대해 이야기해 보겠습니다. (2)

    사실 매우 간단합니다. 케이스에 타이머를 설정하기만 하면 됩니다.

    이제 언급했으니 케이스에도 사용되는 또 다른 사용법을 추가하겠습니다Go의 동시 프로그래밍에 대해 이야기해 보겠습니다. (2) := time.Tick(time.Second)

    .

    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的大量练习。

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

    바둑의 바다에서 오랫동안 수영을 하다 보면 자연스레 깨닫게 되는 것들이 있습니다.

    다음 글은 실용적인 동시 크롤러 프로젝트를 제공하는 것입니다.

    배움의 끈기, 쓰기의 끈기, 공유의 끈기는 카카가 창립 이래 줄곧 지켜온 신념입니다. 거대 인터넷에 올라온 카카의 글이 조금이나마 도움이 되었으면 좋겠습니다. 저는 카카입니다. 다음에 만나요.

    위 내용은 Go의 동시 프로그래밍에 대해 이야기해 보겠습니다. (2)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

    성명:
    본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.