ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • golang: for-select 패턴
    Back-End/Golang 2024. 4. 19. 10:24
    반응형

    Go 언어는 효율적인 동시성 처리를 지원하는 강력한 메커니즘을 제공합니다. 이 중 for-select 패턴은 Go의 동시성을 활용하는 중요한 방법 중 하나로, 여러 동시 작업을 관리하고, 다양한 채널 간 통신을 조절하는 데 사용됩니다. 이 블로그 글에서는 for-select 패턴이 무엇인지, 어떻게 사용되는지, 그리고 이 패턴을 통해 어떤 문제를 해결할 수 있는지 상세히 설명하겠습니다.

    for-select 패턴이란?

     for-select 패턴은 Go의 for 루프와 select 문을 결합한 구조입니다. select 문은 여러 채널 오퍼레이션 중 하나가 준비될 때까지 기다리다가 준비된 오퍼레이션을 실행합니다. 이 패턴은 주로 비동기적으로 데이터를 수신하거나, 여러 채널에 걸쳐 동시에 이벤트를 처리할 필요가 있을 때 사용됩니다.

    for { // 무한 반복 또는 특정 범위에 대한 루프
    	select {
        	// 채널에 대한 작업
        }
    }

    패턴의 사용 사례

    package main
    
    import (
        "fmt"
        "time"
    )
    
    func process(channel chan string) {
        for {
            select {
            case msg := <-channel:
                fmt.Println("Received message:", msg)
            case <-time.After(10 * time.Second):
                fmt.Println("Timeout: no messages received")
                return
            }
        }
    }
    
    func main() {
        channel := make(chan string)
        go process(channel)
        channel <- "Hello"
        time.Sleep(5 * time.Second)
        channel <- "World"
        time.Sleep(12 * time.Second)
    }

     이 예제에서는 하나의 채널을 통해 메시지를 받고 처리하는 간단한 for-select 구조를 보여줍니다. select 문은 채널에서 메시지를 받거나 10초의 타임아웃을 처리합니다.

     

    for-select 패턴에서의 특별한 사용 사례

    채널에서 반복 변수 보내기

     채널을 통해 반복 변수를 보내는 경우, 고루틴과 for-select 패턴을 활용하여 동시성을 유지하면서 데이터를 전송할 수 있습니다. 아래 예제에서는 for 루프를 사용하여 반복적으로 변수를 채널에 보내고, select를 사용하여 채널에서 데이터를 받습니다.

    package main
    
    import (
        "fmt"
        "time"
    )
    
    func sendNumbers(ch chan<- int) {
        for i := 0; i < 10; i++ {
            ch <- i
            time.Sleep(1 * time.Second) // 데이터 보내기 간에 딜레이를 추가
        }
        close(ch) // 데이터 전송이 완료되면 채널을 닫습니다.
    }
    
    func main() {
        ch := make(chan int)
        go sendNumbers(ch)
    
        for {
            select {
            case num, ok := <-ch:
                if !ok {
                    fmt.Println("Channel closed!")
                    return
                }
                fmt.Println("Received:", num)
            }
        }
    }

     이 예제에서 sendNumbers 함수는 0부터 9까지의 숫자를 채널로 보내고, 메인 함수의 for-select 루프는 이를 수신하여 출력합니다. 채널이 닫힐 때 ok는 false가 되고, 이를 감지하여 루프를 종료합니다.

    멈추기를 기다리면서 무한히 대기

     특정 조건이 충족될 때까지 무한히 대기하는 패턴은 주로 데드락을 피하고, 특정 이벤트를 기다리는데 사용됩니다. 아래 예제에서는 stopChan을 사용하여 외부 신호로 멈춤을 제어합니다.

    package main
    
    import (
        "fmt"
        "os"
        "os/signal"
        "syscall"
    )
    
    func main() {
        stopChan := make(chan os.Signal, 1)
        signal.Notify(stopChan, syscall.SIGINT, syscall.SIGTERM)
    
        for {
            select {
            case <-stopChan:
                fmt.Println("Received stop signal")
                return
            default:
                // 이 블록은 CPU를 많이 사용하므로 주의 필요
                fmt.Println("Waiting for stop signal...")
                time.Sleep(1 * time.Second) // 반복 사이에 시간 지연 추가
            }
        }
    }

     이 코드는 운영 체제로부터 인터럽트(SIGINT) 또는 종료(SIGTERM) 신호를 받을 때까지 계속 실행됩니다. select문 내의 default 케이스는 신호가 도착하기 전까지 주기적으로 메시지를 출력하고, 신호가 도착하면 루프에서 벗어나 프로그램을 종료합니다.

     

    고급 사용법: 다중 채널과 동적 채널 선택

    고급 for-select 패턴 사용법은 다양한 채널에서 복잡한 조건과 요구 사항을 처리하는 데 적합합니다. 이 섹션에서는 다중 채널 관리와 동적으로 채널을 선택하는 방법에 대해 설명합니다.

    다중 채널 관리

    다중 채널을 관리하는 경우, 각 채널은 독립적인 데이터 소스 또는 작업을 대표할 수 있습니다. 아래 예제에서는 세 개의 채널을 사용하여 다양한 데이터 소스에서 메시지를 동시에 처리합니다.

    package main
    
    import (
        "fmt"
        "time"
        "math/rand"
    )
    
    func worker(id int, channel chan string) {
        for {
            channel <- fmt.Sprintf("Worker %d: %v", id, rand.Intn(100))
            time.Sleep(time.Duration(rand.Intn(3)) * time.Second)
        }
    }
    
    func main() {
        ch1 := make(chan string)
        ch2 := make(chan string)
        ch3 := make(chan string)
    
        go worker(1, ch1)
        go worker(2, ch2)
        go worker(3, ch3)
    
        for i := 0; i < 10; i++ {
            select {
            case msg1 := <-ch1:
                fmt.Println("Received from ch1:", msg1)
            case msg2 := <-ch2:
                fmt.Println("Received from ch2:", msg2)
            case msg3 := <-ch3:
                fmt.Println("Received from ch3:", msg3)
            case <-time.After(5 * time.Second):
                fmt.Println("No messages received in 5 seconds")
                return
            }
        }
    }

     이 예제에서 worker 함수는 각각의 채널을 통해 랜덤한 숫자를 보내고, main 함수의 select 문은 이 세 채널에서 오는 메시지를 받아 출력합니다. 각 case는 동적으로 선택되며, 5초 동안 메시지가 없으면 타임아웃이 발생합니다.

    동적 채널 선택

     동적 채널 선택은 실행 시간에 따라 채널 선택을 변경할 수 있게 합니다. 이는 reflect.Select를 사용하여 구현할 수 있습니다. 아래는 동적 채널 선택을 위한 기본적인 예제입니다.

    package main
    
    import (
        "fmt"
        "reflect"
        "time"
    )
    
    func main() {
        ch1 := make(chan reflect.SelectCase)
        ch2 := make(chan reflect.SelectCase)
        cases := []reflect.SelectCase{
            {Dir: reflect.SelectRecv, Chan: reflect.ValueOf(ch1)},
            {Dir: reflect.SelectRecv, Chan: reflect.ValueOf(ch2)},
        }
    
        go func() {
            time.Sleep(2 * time.Second)
            ch1 <- reflect.SelectCase{Dir: reflect.SelectDefault}
        }()
    
        go func() {
            time.Sleep(1 * time.Second)
            ch2 <- reflect.SelectCase{Dir: reflect.SelectDefault}
        }()
    
        chosen, value, ok := reflect.Select(cases)
        if !ok {
            fmt.Println("Channel closed")
            return
        }
        fmt.Println("Received from channel:", chosen, "value:", value)
    }

    이 코드는 reflect 패키지를 사용해 실행 시간에 채널 선택을 변경합니다. 각 고루틴은 다른 시간에 채널에 메시지를 보내고, reflect.Select는 준비된 채널에서 메시지를 받습니다.

     

    패턴의 장점과 사용 시 주의점

    장점:

    • 다양한 채널 소스에서의 동시 데이터 처리가 가능합니다.
    • 타임아웃과 같은 시간 기반 작업을 쉽게 구현할 수 있습니다.

    주의점:

    • select 문은 여러 케이스가 동시에 실행될 준비가 되어 있을 경우, 무작위로 하나를 선택합니다. 이는 예측하지 못한 동작을 초래할 수 있으므로 주의가 필요합니다.
    • 무한 루프 내에서 select를 사용할 경우 리소스 고갈 문제에 유의해야 합니다.

    결론

     for-select 패턴은 Go의 동시성 프로그래밍에서 꼭 필요한 요소입니다. 이를 통해 복잡한 동시성 문제를 간결하고 효과적으로 해결할 수 있으며, Go 프로그램의 성능을 극대화할 수 있습니다. 이 패턴을 잘 이해하고 사용한다면, Go의 강력한 동시성 기능을 최대한 활용할 수 있습니다.

    반응형

    'Back-End > Golang' 카테고리의 다른 글

    golang: Go 언어의 장점  (1) 2024.04.21
    golang: 문자열 함수  (0) 2024.04.21
    golang: 동시성에서 제한(Confinement)이란?  (0) 2024.04.19
    golang: 포인터(Pointer)란?  (0) 2024.03.30
    golang: 컨텍스트(Context)란?  (0) 2024.03.30
Designed by Tistory.