[Go] Channel?

윤동환·2023년 7월 4일
0

Go

목록 보기
5/18
post-thumbnail

Channel

channel은 채널 연산지인 <-를 통해 값을 주고 받을 수 있는 하나의 분리된 통로입니다. Go의 핵심 가치관인 CSP스타일의 Concurrent 프로그래밍을 지원하기위해 제공되며, Goroutine간 데이터를 전달하는 역할로 사용됩니다.

ch <- v // channel ch에 v를 전송한다.
v := <- ch // ch로 부터 값을 받고, 값을 v에 대입한다.
//데이터는 화살표의 방향대로 흐릅니다.

channel은 map과 slice처럼 사용하기 전에 생성되어야만 합니다.

채널은 make()를 통해 생성할 수 있습니다.

ch := make(chan int)

기본적으로 전송과 수신은 다른 한 쪽이 준비될 때까지 block상태입니다.
이는 명시적인 lock이나 조건 변수 없이 goroutine이 synchronous하게 작업될 수 있도록 합니다.

예제 코드는 두개의 goroutine에 작업을 분산시키면서 slice 있는 숫자들을 더합니다. 두 goroutine이그들의 연산을 완료하면 최종 결과를 계산합니다.

예제

package main

import (
	"fmt"
)
func sum(s []int, c chan int) {
	sum := 0
	for _, v := range s {
		sum += v
	}
	c <- sum // send sum to c
}

func main() {
	s := []int{7, 2, 8, -9, 4, 0}

	c := make(chan int)
	go sum(s[:len(s)/2], c)
	go sum(s[len(s)/2:], c)
	x, y := <-c, <-c // receive from c

	fmt.Println(x, y, x+y)
}

결과

먼저 호출한 sum에서 c에 저장한 값이 7 + 2 + 8 이므로 17
그 다음 호출한 sum에서 c에 저장한 값이 -9 + 4 + 0이므로 -5
그 둘을 더한값이 17 -5 = 12이므로 17, -5, 12가 출력될 것이라 예상했으나
-5가 먼저출력되었다.

	go sum(s[:len(s)/2], c)
	time.Sleep(100 * time.Millisecond)
	go sum(s[len(s)/2:], c)

수정 1

위처럼 코드를 수정하였더니

결과

예상한 출력값이 나왔다.

수정 2

sum 함수를 아래와 같이 수정해보았다.

func sum(s []int, c chan int) {
	sum := 0
	for _, v := range s {
		sum += v
		fmt.Println(v)
	}
	c <- sum // send sum to c
}
...
	go sum(s[:len(s)/2], c)
	go sum(s[len(s)/2:], c)

결과


실행 자체가 두번째 sum이 먼저 되었다. 그 이유를 아시는 분은 알려주시면 감사하겠습니다.

Bufferd Channels

Channel은 bufferd될 수 있습니다.
즉, Channel이 버퍼를 가질 수 있다는 의미입니다.
bufferd channel을 초기화 하기 위해선 make의 두번째 인자로 buffer 길이를 제공하면 됩니다.

ch := make(chan int, 100)

예제

예제 1-1

buffer channel로 전송은 그 buffer의 사이즈가 다 찼을 경우에만 block됩니다.

package main

import "fmt"

func main() {
	ch := make(chan int, 1)
	ch <- 1
	ch <- 2
	fmt.Println(<-ch)
	fmt.Println(<-ch)
}

결과

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main()
	/tmp/sandbox1722270452/prog.go:8 +0x4b

Program exited: status 2.

버퍼의 크기가 1로써 버퍼가 가득 차 block상태가 되어 더이상 값을 넣을 수 없다.

예제 1-2

package main

import "fmt"

func main() {
	ch := make(chan int, 1)
	ch <- 1
	k := <- ch
	ch <- 2
	fmt.Println(<-ch)
	fmt.Println(k)
}

결과


먼저 넣은 1을 k에 빼주고 2를 넣으므로써 ch buffer의 크기를 넘지 않도록 하였다.

예제 2

buffer로 부터의 수신은 그 buffer가 비어잇을 때 block됩니다.

package main

import "fmt"

func main() {
	ch := make(chan int, 1)
	ch <- 1
	k := <- ch
	q := <- ch
	ch <- 2
	fmt.Println(<-ch)
	fmt.Println(k)
	fmt.Println(q)
}

결과

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:
main.main()
	/tmp/sandbox4180547330/prog.go:9 +0x74

Program exited: status 2.

9번째 줄 q := <- ch에서 에러가 발생했다.
왜냐하면, 이미 ch의 버퍼는 비어있어 block되어있기 때문이다.

reference

https://go-tour-ko.appspot.com/concurrency/2

profile
모르면 공부하고 알게되면 공유하는 개발자

0개의 댓글