1. 모든 예제 실행: 코드만 읽지 마세요. 이를 입력하고 실행한 후 동작을 관찰하세요.⚠️ 이 시리즈는 어떻게 진행되나요?
2. 실험 및 깨기: 절전 모드를 제거하고 어떤 일이 일어나는지 확인하고, 채널 버퍼 크기를 변경하고, 고루틴 수를 수정합니다.
깨뜨리는 일을 통해 작동 방식을 배울 수 있습니다
3. 동작에 대한 이유: 수정된 코드를 실행하기 전에 결과를 예측해 보세요. 예상치 못한 동작이 보이면 잠시 멈춰서 그 이유를 생각해 보세요. 설명에 도전해보세요.
4. 정신 모델 구축: 각 시각화는 개념을 나타냅니다. 수정된 코드에 대해 자신만의 다이어그램을 그려보세요.
다음 내용은 "Go 동시성 마스터하기" 시리즈의 1부입니다.
기본부터 시작하여 이를 효과적으로 사용하는 방법에 대한 직관을 발전시켜 나가겠습니다.
조금 길어질 예정이니, 꽤 길 것이므로 준비하세요.
우리는 모든 과정을 직접 진행합니다.
여러 파일을 다운로드하는 간단한 프로그램부터 시작해 보겠습니다.
package main import ( "fmt" "time" ) func downloadFile(filename string) { fmt.Printf("Starting download: %s\n", filename) // Simulate file download with sleep time.Sleep(2 * time.Second) fmt.Printf("Finished download: %s\n", filename) } func main() { fmt.Println("Starting downloads...") startTime := time.Now() downloadFile("file1.txt") downloadFile("file2.txt") downloadFile("file3.txt") elapsedTime := time.Since(startTime) fmt.Printf("All downloads completed! Time elapsed: %s\n", elapsedTime) }
다음 다운로드가 시작되기 전에 각 2초 다운로드가 완료되어야 하기 때문에 프로그램에 총 6초가 소요됩니다. 이를 시각화해 보겠습니다.
이 시간을 줄일 수 있습니다. go 루틴을 사용하도록 프로그램을 수정해 보겠습니다.
주의사항: 함수 호출 전에 키워드를 사용하세요
package main import ( "fmt" "time" ) func downloadFile(filename string) { fmt.Printf("Starting download: %s\n", filename) // Simulate file download with sleep time.Sleep(2 * time.Second) fmt.Printf("Finished download: %s\n", filename) } func main() { fmt.Println("Starting downloads...") // Launch downloads concurrently go downloadFile("file1.txt") go downloadFile("file2.txt") go downloadFile("file3.txt") fmt.Println("All downloads completed!") }
잠깐만요? 아무것도 인쇄되지 않았나요? 왜요?
무슨 일이 일어나고 있는지 이해하기 위해 이를 시각화해 보겠습니다.
위의 시각화를 통해 우리는 고루틴이 완료되기 전에 주요 기능이 존재한다는 것을 이해합니다. 한 가지 관찰은 모든 고루틴의 수명 주기가 주요 기능에 의존한다는 것입니다.
참고: 주요 기능 자체는 고루틴입니다 ;)
이 문제를 해결하려면 기본 고루틴이 다른 고루틴이 완료될 때까지 기다리도록 하는 방법이 필요합니다. 이를 수행하는 방법에는 여러 가지가 있습니다:
go 루틴이 완료될 때까지 몇 초 동안 기다리세요.
package main import ( "fmt" "time" ) func downloadFile(filename string) { fmt.Printf("Starting download: %s\n", filename) // Simulate file download with sleep time.Sleep(2 * time.Second) fmt.Printf("Finished download: %s\n", filename) } func main() { fmt.Println("Starting downloads...") startTime := time.Now() downloadFile("file1.txt") downloadFile("file2.txt") downloadFile("file3.txt") elapsedTime := time.Since(startTime) fmt.Printf("All downloads completed! Time elapsed: %s\n", elapsedTime) }
이 문제는 고루틴에 얼마나 많은 시간이 걸릴지 알 수 없다는 것입니다. 각각에 대한 시간은 일정하지만 실제 시나리오에서는 다운로드 시간이 다양하다는 것을 알고 있습니다.
Go의 sync.WaitGroup은 고루틴 모음의 실행이 완료될 때까지 기다리는 데 사용되는 동시성 제어 메커니즘입니다.
이것을 실제로 보고 시각화해 보겠습니다.
package main import ( "fmt" "time" ) func downloadFile(filename string) { fmt.Printf("Starting download: %s\n", filename) // Simulate file download with sleep time.Sleep(2 * time.Second) fmt.Printf("Finished download: %s\n", filename) } func main() { fmt.Println("Starting downloads...") // Launch downloads concurrently go downloadFile("file1.txt") go downloadFile("file2.txt") go downloadFile("file3.txt") fmt.Println("All downloads completed!") }
이를 시각화하고 sync.WaitGroup의 작동을 이해해 보겠습니다.
카운터 메커니즘:
동기화 흐름:
피해야 할 일반적인 함정
package main
import (
"fmt"
"time"
)
func downloadFile(filename string) {
fmt.Printf("Starting download: %s\n", filename)
// Simulate file download with sleep
time.Sleep(2 * time.Second)
fmt.Printf("Finished download: %s\n", filename)
}
func main() {
fmt.Println("Starting downloads...")
startTime := time.Now() // Record start time
go downloadFile("file1.txt")
go downloadFile("file2.txt")
go downloadFile("file3.txt")
// Wait for goroutines to finish
time.Sleep(3 * time.Second)
elapsedTime := time.Since(startTime)
fmt.Printf("All downloads completed! Time elapsed: %s\n", elapsedTime)
}
그래서 우리는 고루틴이 어떻게 작동하는지 잘 이해했습니다. 아니 투고 루틴은 어떻게 통신하나요? 채널이 들어오는 곳입니다.
Go의채널은 고루틴 간의 통신에 사용되는 강력한 동시성 기본 요소입니다. 이는 고루틴이 데이터를 안전하게 공유할 수 있는 방법을 제공합니다.
채널을 파이프로 생각하세요: 한 고루틴은 데이터를 채널로 보낼 수 있고 다른 고루틴은 이를 받을 수 있습니다.
몇 가지 속성은 다음과 같습니다.
package main import ( "fmt" "time" ) func downloadFile(filename string) { fmt.Printf("Starting download: %s\n", filename) // Simulate file download with sleep time.Sleep(2 * time.Second) fmt.Printf("Finished download: %s\n", filename) } func main() { fmt.Println("Starting downloads...") startTime := time.Now() downloadFile("file1.txt") downloadFile("file2.txt") downloadFile("file3.txt") elapsedTime := time.Since(startTime) fmt.Printf("All downloads completed! Time elapsed: %s\n", elapsedTime) }
ch <- "hello"가 교착 상태를 일으키는 이유는 무엇입니까? 채널이 본질적으로 차단되어 있고 여기에서 "hello"를 전달하고 있으므로 수신기가 있을 때까지 기본 고루틴을 차단하고 수신기가 없기 때문에 멈추게 됩니다.
고루틴을 추가하여 이 문제를 해결해 보겠습니다
package main import ( "fmt" "time" ) func downloadFile(filename string) { fmt.Printf("Starting download: %s\n", filename) // Simulate file download with sleep time.Sleep(2 * time.Second) fmt.Printf("Finished download: %s\n", filename) } func main() { fmt.Println("Starting downloads...") // Launch downloads concurrently go downloadFile("file1.txt") go downloadFile("file2.txt") go downloadFile("file3.txt") fmt.Println("All downloads completed!") }
이것을 시각화해 보겠습니다.
이번 메시지는 다른 고루틴에서 전송되므로 메인이 차단되지 않습니다 채널로 보내는 동안 msg := <-ch로 이동하여 메시지를 수신할 때까지 메인 고루틴을 차단합니다. 메시지.
이제 채널을 사용하여 파일 다운로드 문제를 해결해 보겠습니다(메인은 다른 사람이 완료할 때까지 기다리지 않습니다).
package main import ( "fmt" "time" ) func downloadFile(filename string) { fmt.Printf("Starting download: %s\n", filename) // Simulate file download with sleep time.Sleep(2 * time.Second) fmt.Printf("Finished download: %s\n", filename) } func main() { fmt.Println("Starting downloads...") startTime := time.Now() // Record start time go downloadFile("file1.txt") go downloadFile("file2.txt") go downloadFile("file3.txt") // Wait for goroutines to finish time.Sleep(3 * time.Second) elapsedTime := time.Since(startTime) fmt.Printf("All downloads completed! Time elapsed: %s\n", elapsedTime) }
시각화:
더 잘 이해하기 위해 연습을 해보겠습니다.
프로그램 시작:
메인 고루틴이 완료된 채널을 생성합니다
3개의 다운로드 고루틴 실행
각 고루틴은 동일한 채널에 대한 참조를 얻습니다
다운로드 실행:
채널 루프:
루프 동작:
완료순서는 상관없습니다!
관찰:
⭐ 각 전송(완료 <- true)에는 정확히 한 번의 수신(<-done)
이 있습니다. ⭐ 메인 고루틴은 루프를 통해 모든 것을 조정합니다
우리는 이미 두 개의 고루틴이 어떻게 통신할 수 있는지 살펴보았습니다. 언제? 그동안. 주요 기능도 고루틴이라는 점을 잊지 마세요.
package main import ( "fmt" "time" ) func downloadFile(filename string) { fmt.Printf("Starting download: %s\n", filename) // Simulate file download with sleep time.Sleep(2 * time.Second) fmt.Printf("Finished download: %s\n", filename) } func main() { fmt.Println("Starting downloads...") startTime := time.Now() downloadFile("file1.txt") downloadFile("file2.txt") downloadFile("file3.txt") elapsedTime := time.Since(startTime) fmt.Printf("All downloads completed! Time elapsed: %s\n", elapsedTime) }
이것을 시각화하고 시험해 보겠습니다.
모의 실행:
프로그램 시작(t=0ms)
첫 번째 메시지(t=1ms)
두 번째 메시지(t=101ms)
세 번째 메시지(t=201ms)
채널 닫기(t=301ms)
완료(t=302-303ms)
버퍼 채널이 필요한 이유는 무엇인가요?
버퍼링되지 않은 채널은 상대방이 준비될 때까지 발신자와 수신자를 모두 차단합니다. 고주파수 통신이 필요한 경우 두 고루틴이 데이터 교환을 위해 일시 중지되어야 하므로 버퍼링되지 않은 채널이 병목 현상을 일으킬 수 있습니다.
버퍼 채널 속성:
실제로 확인해보세요:
package main import ( "fmt" "time" ) func downloadFile(filename string) { fmt.Printf("Starting download: %s\n", filename) // Simulate file download with sleep time.Sleep(2 * time.Second) fmt.Printf("Finished download: %s\n", filename) } func main() { fmt.Println("Starting downloads...") startTime := time.Now() downloadFile("file1.txt") downloadFile("file2.txt") downloadFile("file3.txt") elapsedTime := time.Since(startTime) fmt.Printf("All downloads completed! Time elapsed: %s\n", elapsedTime) }
출력(ch<-"third" 주석 처리를 해제하기 전)
왜 메인 고루틴을 차단하지 않았나요?
버퍼 채널을 사용하면 발신자를 차단하지 않고 최대 용량까지 전송할 수 있습니다.
채널의 용량은 2입니다. 즉, 차단하기 전에 버퍼에 두 개의 값을 보유할 수 있습니다.
'첫 번째'와 '두 번째'로 버퍼가 이미 가득 찼습니다. 이 값을 소비하는 동시 수신자가 없기 때문에 전송 작업이 무기한 차단됩니다.
메인 고루틴도 전송을 담당하고 채널에서 값을 수신할 다른 활성 고루틴이 없기 때문에 프로그램은 세 번째 메시지를 보내려고 할 때 교착 상태에 들어갑니다.
세 번째 메시지의 주석 처리를 제거하면 현재 용량이 가득 차서 교착 상태가 발생하고 세 번째 메시지는 버퍼가 해제될 때까지 차단됩니다.
Aspect | Buffered Channels | Unbuffered Channels |
---|---|---|
Purpose | For decoupling sender and receiver timing. | For immediate synchronization between sender and receiver. |
When to Use | - When the sender can proceed without waiting for receiver. | - When sender and receiver must synchronize directly. |
- When buffering improves performance or throughput. | - When you want to enforce message-handling immediately. | |
Blocking Behavior | Blocks only when buffer is full. | Sender blocks until receiver is ready, and vice versa. |
Performance | Can improve performance by reducing synchronization. | May introduce latency due to synchronization. |
Example Use Cases | - Logging with rate-limited processing. | - Simple signaling between goroutines. |
- Batch processing where messages are queued temporarily. | - Hand-off of data without delay or buffering. | |
Complexity | Requires careful buffer size tuning to avoid overflows. | Simpler to use; no tuning needed. |
Overhead | Higher memory usage due to the buffer. | Lower memory usage; no buffer involved. |
Concurrency Pattern | Asynchronous communication between sender and receiver. | Synchronous communication; tight coupling. |
Error-Prone Scenarios | Deadlocks if buffer size is mismanaged. | Deadlocks if no goroutine is ready to receive or send. |
다음과 같은 경우 버퍼 채널을 사용하세요.
다음과 같은 경우 버퍼되지 않은 채널을 사용하세요.
이러한 기본 사항은 더욱 발전된 개념을 위한 기반을 마련합니다. 다음 게시물에서는 다음 내용을 살펴보겠습니다.
다음 게시물:
Go의 강력한 동시성 기능에 대한 이해를 계속 쌓아가는 동안 계속 지켜봐 주시기 바랍니다!
위 내용은 직관적인 시각 자료로 Golang의 고루틴과 채널 이해하기의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!