목차
Go의 고루틴과 채널에 대해 이야기해 봅시다
백엔드 개발 Golang Go의 동시 프로그래밍에 대해 이야기해 보겠습니다. (2)

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

Jul 07, 2021 pm 04:16 PM
go

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으로 문의하세요.

    핫 AI 도구

    Undresser.AI Undress

    Undresser.AI Undress

    사실적인 누드 사진을 만들기 위한 AI 기반 앱

    AI Clothes Remover

    AI Clothes Remover

    사진에서 옷을 제거하는 온라인 AI 도구입니다.

    Undress AI Tool

    Undress AI Tool

    무료로 이미지를 벗다

    Clothoff.io

    Clothoff.io

    AI 옷 제거제

    Video Face Swap

    Video Face Swap

    완전히 무료인 AI 얼굴 교환 도구를 사용하여 모든 비디오의 얼굴을 쉽게 바꾸세요!

    뜨거운 도구

    메모장++7.3.1

    메모장++7.3.1

    사용하기 쉬운 무료 코드 편집기

    SublimeText3 중국어 버전

    SublimeText3 중국어 버전

    중국어 버전, 사용하기 매우 쉽습니다.

    스튜디오 13.0.1 보내기

    스튜디오 13.0.1 보내기

    강력한 PHP 통합 개발 환경

    드림위버 CS6

    드림위버 CS6

    시각적 웹 개발 도구

    SublimeText3 Mac 버전

    SublimeText3 Mac 버전

    신 수준의 코드 편집 소프트웨어(SublimeText3)

    Go WebSocket 메시지를 보내는 방법은 무엇입니까? Go WebSocket 메시지를 보내는 방법은 무엇입니까? Jun 03, 2024 pm 04:53 PM

    Go에서는 gorilla/websocket 패키지를 사용하여 WebSocket 메시지를 보낼 수 있습니다. 특정 단계: WebSocket 연결을 설정합니다. 문자 메시지 보내기: WriteMessage(websocket.TextMessage,[]byte("Message"))를 호출합니다. 바이너리 메시지 보내기: WriteMessage(websocket.BinaryMessage,[]byte{1,2,3})를 호출합니다.

    Golang 함수 수명주기 및 변수 범위에 대한 심층적인 이해 Golang 함수 수명주기 및 변수 범위에 대한 심층적인 이해 Apr 19, 2024 am 11:42 AM

    Go에서 함수 수명주기에는 정의, 로드, 연결, 초기화, 호출 및 반환이 포함됩니다. 변수 범위는 함수 수준과 블록 수준으로 구분됩니다. 함수 내의 변수는 내부적으로 표시되지만 블록 내의 변수는 블록 내에서만 표시됩니다. .

    Go에서 정규식을 사용하여 타임스탬프를 일치시키는 방법은 무엇입니까? Go에서 정규식을 사용하여 타임스탬프를 일치시키는 방법은 무엇입니까? Jun 02, 2024 am 09:00 AM

    Go에서는 정규식을 사용하여 타임스탬프를 일치시킬 수 있습니다. ISO8601 타임스탬프를 일치시키는 데 사용되는 것과 같은 정규식 문자열을 컴파일합니다. ^\d{4}-\d{2}-\d{2}T \d{ 2}:\d{2}:\d{2}(\.\d+)?(Z|[+-][0-9]{2}:[0-9]{2})$ . regexp.MatchString 함수를 사용하여 문자열이 정규식과 일치하는지 확인합니다.

    Golang과 Go 언어의 차이점 Golang과 Go 언어의 차이점 May 31, 2024 pm 08:10 PM

    Go와 Go 언어는 서로 다른 특성을 지닌 서로 다른 개체입니다. Go(Golang이라고도 함)는 동시성, 빠른 컴파일 속도, 메모리 관리 및 크로스 플랫폼 이점으로 유명합니다. Go 언어의 단점은 다른 언어에 비해 생태계가 덜 풍부하고 구문이 더 엄격하며 동적 타이핑이 부족하다는 점입니다.

    Golang 기술 성능 최적화에서 메모리 누수를 방지하는 방법은 무엇입니까? Golang 기술 성능 최적화에서 메모리 누수를 방지하는 방법은 무엇입니까? Jun 04, 2024 pm 12:27 PM

    메모리 누수로 인해 파일, 네트워크 연결, 데이터베이스 연결 등 더 이상 사용하지 않는 리소스를 닫는 방식으로 Go 프로그램 메모리가 지속적으로 증가할 수 있습니다. 더 이상 강력하게 참조되지 않는 경우 약한 참조를 사용하여 메모리 누수 및 가비지 수집 대상 개체를 방지합니다. go 코루틴을 사용하면 메모리 누수를 방지하기 위해 종료 시 코루틴 스택 메모리가 자동으로 해제됩니다.

    IDE에서 Golang 함수 문서를 보는 방법은 무엇입니까? IDE에서 Golang 함수 문서를 보는 방법은 무엇입니까? Apr 18, 2024 pm 03:06 PM

    IDE를 사용하여 Go 함수 문서 보기: 함수 이름 위에 커서를 놓습니다. 단축키(GoLand: Ctrl+Q, VSCode: GoExtensionPack 설치 후 F1을 누르고 "Go:ShowDocumentation" 선택)를 누릅니다.

    단위 테스트 Go 동시 기능 가이드 단위 테스트 Go 동시 기능 가이드 May 03, 2024 am 10:54 AM

    단위 테스트 동시 기능은 동시 환경에서 올바른 동작을 보장하는 데 도움이 되므로 매우 중요합니다. 동시 기능을 테스트할 때는 상호 배제, 동기화, 격리와 같은 기본 원칙을 고려해야 합니다. 동시 기능은 경쟁 조건을 시뮬레이션하고, 테스트하고, 결과를 확인하여 단위 테스트할 수 있습니다.

    Golang 함수가 맵 매개변수를 수신할 때 주의할 사항 Golang 함수가 맵 매개변수를 수신할 때 주의할 사항 Jun 04, 2024 am 10:31 AM

    Go의 함수에 지도를 전달하면 기본적으로 복사본이 생성되며 복사본을 수정해도 원본 지도에는 영향을 미치지 않습니다. 원본 지도를 수정해야 하는 경우 포인터를 통해 전달할 수 있습니다. 빈 맵은 기술적으로 nil 포인터이기 때문에 주의해서 처리해야 하며, 비어 있지 않은 맵을 기대하는 함수에 빈 맵을 전달하면 오류가 발생합니다.

    See all articles