🔥 Default Selection

541자
7분

Go의 select 문은 여러 개의 채널에서 동시에 데이터를 기다리거나 보낼 수 있게 해줍니다. 하지만 만약 select에서 기다리는 채널 중 어떤 채널도 준비되지 않았다면 어떻게 될까요? 이런 경우에는 default 케이스가 실행됩니다.

default 케이스는 select에서 다른 케이스가 준비되지 않았을 때 실행되는 일종의 "백업 플랜"과 같습니다. 이를 활용하면 채널에서 값 받기를 시도할 때 블로킹 없이 바로 결과를 얻을 수 있습니다.

예를 들어, 다음과 같이 default 케이스를 사용할 수 있습니다:

select {
case i := <-c:
    // c에서 값을 받아 i에 저장
default:
    // c에서 값 받기를 시도했지만 블로킹되므로 default 케이스 실행
}
 
go

위 코드에서는 채널 c에서 값을 받으려고 시도합니다. 만약 c에 값이 준비되어 있다면 그 값을 i에 저장하고, 그렇지 않다면 default 케이스를 실행하여 블로킹 없이 바로 다음 코드로 넘어갑니다.

default 케이스의 활용 예시를 보다 자세히 알아보기 위해 다음 코드를 살펴봅시다:

package main
 
import (
    "fmt"
    "time"
)
 
func main() {
    tick := time.Tick(100 * time.Millisecond)
    boom := time.After(500 * time.Millisecond)
 
    for {
        select {
        case <-tick:
            fmt.Println("tick.")
        case <-boom:
            fmt.Println("BOOM!")
            return
        default:
            fmt.Println("    .")
            time.Sleep(50 * time.Millisecond)
        }
    }
}
 
go

이 코드는 다음과 같이 동작합니다:

  1. tickboom이라는 두 개의 채널을 생성합니다.
    • tick은 100ms마다 값을 만들어내는 채널입니다.
    • boom은 500ms 후에 값을 만들어내는 채널입니다.
  2. 무한 루프를 시작합니다.
  3. 루프 내에서 select를 사용하여 tick, boom, 그리고 default 케이스를 기다립니다.
    • tick에서 값을 받으면 "tick."을 출력합니다.
    • boom에서 값을 받으면 "BOOM!"을 출력하고 프로그램을 종료합니다.
    • tickboom 모두 값이 없다면 default 케이스를 실행하여 " ."을 출력하고 50ms 동안 대기합니다.
  4. 2번으로 돌아가 루프를 계속 반복합니다.

이 코드를 실행하면 다음과 같은 출력 결과를 얻을 수 있습니다:

    .
    .
tick.
    .
    .
tick.
    .
    .
tick.
    .
    .
tick.
    .
    .
tick.
BOOM!
text

초기에는 tickboom 모두 값이 없으므로 default 케이스가 실행되어 " ."이 출력됩니다. 100ms마다 tick에서 값을 받아 "tick."을 출력하고, 500ms가 지나면 boom에서 값을 받아 "BOOM!"을 출력한 후 프로그램이 종료됩니다.

이렇게 default 케이스를 사용하면 select에서 기다리는 채널들이 모두 블로킹되는 상황에서도 계속 진행할 수 있습니다. 상황에 따라 적절히 활용한다면 더욱 유연하고 능동적인 프로그램을 작성할 수 있겠죠? 예를 들어 GUI 시스템의 이벤트 루프를 간단하게 시뮬레이션 할 수 있습니다.

GUI 시스템의 이벤트 루프 구현

Go 채널과 select를 활용하면 GUI 시스템의 이벤트 루프를 간결하고 효과적으로 구현할 수 있습니다. 다음은 간단한 예시 코드입니다:

package main
 
import (
    "fmt"
)
 
func main() {
    // 이벤트 채널 생성
    renderCh := make(chan string)
    systemNotificationCh := make(chan string)
 
    // 이벤트 발생 시뮬레이션 (goroutine)
    go func() {
        renderCh <- "Render Requested"
        systemNotificationCh <- "System Notification Received"
    }()
 
    // 이벤트 루프
    for {
        select {
        case event := <-renderCh:
            fmt.Println("Render Event:", event)
        case event := <-systemNotificationCh:
            fmt.Println("System Notification Event:", event)
        default:
            // 이벤트가 없을 때 다른 작업 수행 가능
            fmt.Println("No events, doing other work...")
        }
    }
}
 
go

위 코드는 다음과 같이 동작합니다:

  1. renderChsystemNotificationCh라는 두 개의 이벤트 채널을 생성합니다.
  2. 고루틴에서 이벤트 발생을 시뮬레이션합니다.
    • renderCh에 "Render Requested" 이벤트를 보냅니다.
    • systemNotificationCh에 "System Notification Received" 이벤트를 보냅니다.
  3. 이벤트 루프를 시작합니다.
    • select를 사용하여 renderCh, systemNotificationCh, 그리고 default 케이스를 기다립니다.
    • renderChsystemNotificationCh에서 이벤트를 받으면 해당 이벤트를 출력합니다.
    • 이벤트가 없다면 default 케이스를 실행하여 다른 작업을 수행할 수 있습니다.
  4. 이벤트 루프는 계속 반복되며, 이벤트 채널에서 이벤트를 기다립니다.

이런 식으로 Go 채널과 select를 사용하면 GUI 시스템의 이벤트 루프를 간단하고 직관적으로 구현할 수 있습니다. 실제 GUI 라이브러리에서는 좀 더 복잡한 방식으로 구현되겠지만, 기본 개념은 유사합니다.