무지개곰
article thumbnail
반응형

Go 언어에서 고루틴 간에 안전하게 데이터를 전송하고 동기화하는 방법으로 채널이 있습니다.

채널을 생성하고 사용하는 방법, 채널의 다양한 활용 사례에 대하여 알아보겠습니다.

목차

채널이란?

콘텍스트란?


채널이란?

채널은 고루틴끼리 데이터를 주고받을 수 있는 메시지 큐입니다. 이전에 뮤텍스를 활용하여 변수를 잠겄던 방법은 데드락이 발생할 수 있었습니다. 하지만 채널은 특성상 데드락 발생 가능성이 크게 줄어듭니다.

채널 생성

var data1 chan str = make(chan string)
data2 := make(chan int, 2)

채널은 make를 이용하여 생성합니다. make안에 채널타입이라는 것을 알리기 위한 chan과 채널에 넣을 type을 지정합니다.

채널을 data1처럼 생성하는 경우 채널의 크기는 0이 됩니다.

data2의 경우 채널의 크기를 2로 지정하였습니다.

채널에 데이터를 넣을 때 채널 크기 이상의 데이터를 입력하는 경우 채널에서 데이터를 사용하지 않으면 데이터는 입력에서 처리를 기다리게 됩니다. 이러한 경우 계속되는 대기로 인하여 데드락이 발생합니다.

채널에 데이터 입출력

data1 <- "first Data"	// 데이터 입력
getData = <- data1	// 데이터 출력

채널에서 데이터를 다룰 때 '<-'를 사용합니다. 오른쪽에 사용하면 데이터를 입력하는 것이고 왼쪽에서 사용하면 데이터를 출력합니다.

채널 예시 코드 1

package main

import (
	"fmt"
	"sync"
	"time"
)

func readData(wg *sync.WaitGroup, data chan string) {
	time.Sleep(time.Second)
	getData := <-data
	fmt.Println(getData)

	wg.Done()
}
func main() {
	data := make(chan string)
	var wg sync.WaitGroup
	wg.Add(1)

	go readData(&wg, data)
	data <- "first Data"

	wg.Wait()
}

1. main에서 data라는 채널을 생성하였습니다.

2. readData함수에 data를 매개변수로 전달하였고 data에 "first Data"를 입력합니다.

3. readData가 실행되면 1초 후 data에서 값을 출력하여 getData에 저장하고 Println으로 값을 확인합니다.

채널 예시코드 2

func readData(wg *sync.WaitGroup, data chan string) {
	for getData := range data{
		time.Sleep(time.Second)
		fmt.Println(getData)
	}
	wg.Done()
}
func main() {
	data := make(chan string)
	var wg sync.WaitGroup
	wg.Add(1)

	go readData(&wg, data)
	for i:=0 ; i<5 ; i++ {
		data <- fmt.Sprintf("%d번째 Data",i+1)
	}
	close(data)
	
    wg.Wait()
}

for문을 이용하여 채널에 값을 넣을 수 있고 고루틴에서도 for문을 이용하여 channel의 입력을 대기하였다가 입력되면 값을 사용할 수 있습니다. 채널에 더 이상 값이 없는 경우 for문을 빠져나오게 됩니다. 이 경우 채널이 더 필요 없기 때문에 close를 이용하여 채널을 종료합니다.

select 사용

select문은 여러 채널 간의 비동기적인 통신을 관찰하면서 수신 가능한 채널과 그에 대응하는 코드 블록을 실행하는 데 사용됩니다.

func readData(wg *sync.WaitGroup, data1 chan string, data2 chan string) {
    for {
        select {
        case getData := <-data1 :
            fmt.Println(getData)
            time.Sleep(time.Second)
        case quit := <-data2:
            if quit == "quit" {
           	    fmt.Println("data2의 값 : ",quit)
          	    wg.Done()
           	    return
            }
        }
    }
}
func main() {
    data1 := make(chan string)
    data2 := make(chan string)
    var wg sync.WaitGroup
    wg.Add(1)

    go readData(&wg, data1, data2)
    for i:=0 ; i<5 ; i++ {
        data1 <- fmt.Sprintf("%d번째 Data",i+1)
    }
    data2 <- "quit"
    
    wg.Wait()
}

select는 입력되는 채널에 따라 작업을 나눌 수 있습니다. 위의 경우 data1과 data2에 따라 작업을 나누었습니다. data2에 "quit"이 입력되면 wg.Done을 실행하고 for문을 빠져나옵니다.

출력

1번째 Data
2번째 Data
3번째 Data
4번째 Data
5번째 Data
data2의 값 :  quit

콘텍스트란?

콘텍스트는 고루틴간의 작업을 조율하고 취소, 값, 타임아웃 등을 설정하는 기능을 제공하는 도구입니다.

콘텍스트는 계층 구조를 가질 수 있습니다. 부모 콘텍스트가 취소되면 모든 자식 콘텍스트도 취소됩니다.

예시 코드

package main

import (
	"context"
	"fmt"
	"sync"
	"time"
)

var wg sync.WaitGroup

func worker(ctx context.Context) {
	tick := time.Tick(time.Second)
	fmt.Println("parent Data는 : ",ctx.Value("parent Data"))
	fmt.Println("child Data는 : ",ctx.Value("child Data"))
	for {
		select {
		case <-ctx.Done() :
			fmt.Println("child context Done")
			wg.Done()
		case <- tick:
			fmt.Println("tick toc")
		}
	}
}

func main() {
	wg.Add(1)
	parentCtx := context.Background()
	parentCtx = context.WithValue(parentCtx, "parent Data", "Parent")
	childCtx := context.WithValue(parentCtx, "child Data", "Child")
	childCtx, _ = context.WithTimeout(childCtx, 5*time.Second)

	go worker(childCtx)
	
	if data := parentCtx.Value("parent Data"); data == "Parent"{
		fmt.Println("Main completed")
	}
	wg.Wait()
}

1. parentCtx를 context.Background를 통하여 생성하였습니다.

2. parentCtx가 key는 "parent Data", value는 "Parent"를 가지도록 하였습니다.

3. childCtx를 parentCtx를 기반으로 하며 key는 "Child Data", value는 "Child"를 가지도록 하였습니다.

4. childCtx를 5초 뒤에 종료되도록 설정하였습니다.

5. parentCtx의 key에 해당하는 값을 확인하여 "Main completed"를 출력합니다.

6.worker를 실행시키면 key에 해당하는 값을 출력하고 시간이 흐름에 따라 "tick toc"을 출력하고 ctx가 만료되면 "child context Done"을 출력합니다.

 

childCtx는 parentCtx를 기반으로 작성되었기에 parent Data를 포함하고 있습니다.

콘텍스트를 매개변수로 전달하여 값을 꺼내 쓰는 방법으로 고루틴 간의 작업을 조율하며 값과 제어 흐름을 공유할 수 있습니다.

반응형
profile

무지개곰

@무지개곰

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!