개발바닥

채널(Channel) 본문

GO 언어

채널(Channel)

라이언 2022. 2. 22. 21:56
반응형

Go 언어에서 채널은 고루틴을 연결해주는 통로(파이프)이다.

기본적으로 채널은 양방향이고 고루틴은 채널을 통해 데이터를 주고 받는데 사용되는데, 송신,수신이 준비될 때까지 채널에서 대기함으로써

별도의 lock을 걸지 않고 데이터를 동기화하는데 사용된다.

 

여기서 잠깐!
동기와 비동기 개념에 대해서 확인!
동시성 프로그래밍을 하게되면 가장 크게 대두되는 문제점이 바로 동기화이다.
동기는 말 그대로 동시에 일어난다는 뜻이다. 요청과 그 결과가 동시에 일어난다는 약속인데요. 바로 요청을 하면 시간이 얼마가 걸리던지 요청한 자리에서 결과가 주어져야 한다.
즉, 동기방식은 결과가 주어질 때까지 아무것도 못하고 대기해야 한다.

반대로 비동기는 요청을 보냈을 때 응답 상태와 상관없이 다음 동작을 수행 할 수 있다.

 

채널 생성 

채널은 chan 키워드를 사용하여 만들 수 있고 동일한 유형의 데이터만 전송할 수 있다.

strChan := make(chan string, 10) // string 타입 채널

채널 송수신

<- 연산자의 방향은 데이터 수신 또는 송신 여부를 나타낸다.

myChan <- data // 데이터 송신
data := <-myChan // 데이터 수신

채널 닫기

close() 함수를 사용하여 채널을 닫을 수 있다.

채널을 닫게 되면, 해당 채널로는 더 이상 송신을 할 수 없지만, 채널에 데이터가 있는 한 수신은 가능하다.

채널에 데이터가 존재하는지는 두번째 반환 값(boolean)으로 확인 가능하다.

if chanData, isExist := <- strChan; isExist{
	fmt.Println("데이터 수신 : " + chanData)
}

동기 채널

Go 채널은 수신자와 송신자가 서로를 기다리는 속성때문에, 이를 이용하여 Go루틴이 끝날 때까지 기다리는 기능을 구현할 수 있다.

서로 기다린다는게 어떤 의미일까??

송신은 데이터가 들어올 때까지 대기를 하고 있고, 수신은 데이터를 넣어줄 때까지 대기하고 있는다.

 

func main() {
	done := make(chan bool) // Unbuffered Channel
	count := 10

	go func() {
		for i := 0; i < count; i++ {
			fmt.Println("고루틴 : ", i)
		}
		done <- true
	}()
	<-done
}

위에 함수에서는 고루틴이 2개이다. (메인 함수 고루틴, for문을 수행하는 고루틴)

1. 메인 고루틴은 고루틴을 생성하고 채널에 데이터가 들어올 때까지 대기하고 있는다.(block)

2. for문 수행하는 고루틴은 모든 작업을 완료 후 done에 true 값을 넣는다.

3. 메인 고루틴은 채널에 데이터가 들어 왔으므로 채널에 데이터를 꺼내고 메인 고루틴을 종료한다.

 

채널 버퍼링

Go 채널은 2가지의 채널이 존재한다. Unbuffered Channel과 Buffered Channel이 있다.

Unbuffered 채널은 하나의 수신자가 데이터를 받을 때까지 송신자가 데이터를 보내는 채널에 묶여 있게 된다.(동기 채널)

Buffered 채널은 수신자가 받을 준비가 되지 않았더라도 채널안에 데이터를 보관하고 다른 일을 수행할 수 있다.(비동기 채널)

strChan := make(chan string) // unbuffered channel
strChan2 := make(chan string, 10) // buffered channel

채널에 버퍼를 1개 이상 설정하면 비동기 채널이 생성된다. 비동기 채널은 보내는 쪽에서 버퍼가 가득 차면 실행을 멈추고 대기하며 받는 쪽에서는 버퍼에 값이 없으면 대기한다.

 

func main() {
	done := make(chan int, 3)	//buffered channel
	count := 10

	go func() {
		for i := 0; i < count; i++ {
			done <- i
			fmt.Println("고루틴 : ", i)
		}
	}()
	for i := 0; i < count; i++ {
		<-done
		fmt.Println("메인 고루틴 : ", i)
	}
}

위 코드 실행 결과 아래 이미지와 같다.

계속 실행하다보면 결과가 계속 바뀌는 것을 확인할 수 있다. 

그 이유는 메인 고루틴에서는 채널에 데이터가 들어오기를 기다리고 있고,

함수 고루틴에서는 채널에 버퍼가 가득차기 전까지는 비동기적으로 실행하기 때문이다.

결국, 결과는 어떤 값이 나올지 모른다.

 

채널은 언제 사용할까?

버퍼 채널을 만들고 배치성으로 사용할 수 있다.

예를 들면, 로그성 데이터를 수집해서 보낼 때 배치 성으로 어느정도 용량이 차거나 일정 시간마다 채널에 데이터를 꺼내서 처리하도록 하는 것이다. 또는, 트래픽이 몰렸을 때 채널에 처리할 데이터를 보관하고 순차적으로 처리하는 방법도 있다. 

 

 

반응형

'GO 언어' 카테고리의 다른 글

인터페이스 (interface)  (0) 2022.03.06
모듈(Module)  (0) 2022.03.06
구조체(Struct)  (2) 2022.02.28
슬라이스(Slice)  (0) 2022.02.26
고루틴(goroutine)  (0) 2022.02.21
Comments