요약
- 채널 : 고루틴 끼리 메시지를 전달하는 메시지 큐
- 셀렉트 : 여러개의 채널을 동시에 기다리는 구문
메시지 큐
- 윈도우 프로그래밍을 하면서 경험했던 윈도우 메시지 큐와 동일한 개념이다
- 메시지(다양한 정보)를 Queue에 순차적으로 쌓이게 되고, 순차적으로 처리된다
채널 인스턴스 생성
┏ 채널 타입 ┏ 메시지 타입
var messages chan string = make(chan string)
┗ 채널 인스턴스 변수 ┗ 채널 키워드
- make() 를 통해 생성한다
- 채널 타입은 channel을 의미하는 chan과 메시지 타입을 합쳐서 표현한다
- chan string은 string 타입 메시지를 전달하는 채널의 타입이다
채널에 데이터 넣기
┏ 채널 인스턴스 ┏ 넣을 데이터
messages <- "This is a message"
┗ 연산자
채널에서 데이터 빼기
┏ 빼낸 데이터를 담을 변수 ┏ 채널 인스턴스
var msg string <- messages ┛
┗ 연산자
채널에서 데이터를 하나 넣고 빼는 예제
package main
import (
"fmt"
"sync"
"time"
)
func main()
{
var wg sync.WaitGroup
ch := make(chan int) // 채널 생성
wg.Add(1)
go square(&wg, ch) // 고루틴 생성
ch <- 9 // 채널에 데이터 넣음
wg.Wait() // 작업이 완료되때까지 대기
}
func square(wg *sync.WaitGroup, ch chan int)
{
n := <- ch // 데이터를 빼옴
// 여기서 데이터가 수신될때까지 대기
fmt.Printf("Square: %d \n", n*n) // Output Square: 81
wg.Done() // 작업 완료를 알림
}
채널의 크기
- 일반적으로 채널을 생성하면 크기가 0인 채널이 만들어진다. 크기가 0이라는 뜻은 채널에 들어온 데이터를 담아둘 곳이 없다는 얘기가 된다
- 채널의 크기가 0이면 데이터 수신자가 없으면 데이터 송신자가 대기를 한다
- 채널의 크기가 존재하면 데이터 송신자는 데이터를 넣고 원래의 동작을 수행한다
- 채널의 크기가 존재하지만 채널의 크기를 초과한 데이터가 송신되면 담아둘 곳이 없어서 데이터 송신자는 멈추게 된다
채널에서 데이터를 가져가지 않아서 프로그램이 멈추는 예제
package main
import "fmt"
func main() {
ch := make(chan int) // 크기가 0인 채널 생성
ch <- 9 // 데이터 수신자가 없기 때문에 여기서 멈춤
fmt.Println("Never print") // 실행되지 않는다.
}
크기를 가지는 채널 생성 예제
package main
import f "fmt"
func main() {
f.Println("beffered")
messages := make(chan string, 2) // string 타입의 채널을 만들고 해당 채널의 버퍼는 2이다.
messages <- "wisoft"
messages <- "lab"
f.Println(<-messages)
f.Println(<-messages)
}
make의 두번째 인자에 숫자를 넣어주면 그 크기로 생성된다
채널의 크기를 넘어가는 경우엔?
package main
import f "fmt"
func main() {
f.Println("beffered")
messages := make(chan string, 2)
messages <- "wisoft"
messages <- "lab"
messages <- "golang" // 여기서 deadlock 발생
f.Println(<-messages)
f.Println(<-messages)
f.Println(<-messages)
}
------------ 아래 부터는 영역을 나누는 방법 ------------
채널에서 데이터 대기
package main
import (
"fmt"
"sync"
)
func square(wg *sync.WaitGroup, ch chan int) {
for n := range ch {
fmt.Println("Square: %d \n", n*n)// 2. 데이터를 계속 기다림
}
wg.Done() // 4. 실행되지 않음
}
func main() {
var wg sync.WaitGroup
ch := make(chan int)
wg.Add(1)
go square(&wg, ch)
for i := 0; i < 10; i++ {
ch <- i * 2 // 1. 데이터 송신
}
wg.Wait() // 3. 작업 완료를 기다림
}
위에 코드에서는 for range 구문에서 계속해서 데이터가 들어오기를 기다리기 때문에 4번이 실행되지 않고 deadlock
채널 닫기
package main
import (
"fmt"
"sync"
)
func square(wg *sync.WaitGroup, ch chan int) {
for n := range ch {
fmt.Println("Square: %d \n", n*n)
}
wg.Done()
}
func main() {
var wg sync.WaitGroup
ch := make(chan int)
wg.Add(1)
go square(&wg, ch)
for i := 0; i < 10; i++ {
ch <- i * 2
}
close(ch) // 채널 닫음
wg.Wait()
}
데이터를 모두 넣고 채널을 닫아주면 for range에서는 채널이 닫혀서 더이상 수신을 할 수 없기 때문에 for문을 빠져나오고 정상 종료된다
Select 문
Select문은 여러 채널을 동시에 기다릴 수 있다. 하지만 어떤 채널에서라도 데이터를 읽어오면 해당 구문을 실행하고 select문은 종료된다. 하나의 case만 처리되면 종료되기 때문에 반복해서 데이터를 처리하고 싶다면 for문과 함께 사용해야 한다
select {
case n := <-ch1
... // ch1 채널에서 데이터를 빼낼 수 있을 때 실행
case n := <-ch2
... // ch2 채널에서 데이터를 빼낼 수 있을 때 실행
case ...
}
select를 이용한 데이터 읽고 처리하는 예제
package main
import (
"fmt"
"sync"
)
func square(wg *sync.WaitGroup, ch chan int, quit chan bool) {
for {
select { // ch와 quit 양쪽을 모두 기다림 (non blocking)
case n := <-ch:
fmt.Println("Square: %d \n", n*n)
case <-quit:
wg.Done()
return
default:
fmt.Println("test") // 실행되지 않음
}
}
}
func main() {
var wg sync.WaitGroup
ch := make(chan int)
quit := make(chan bool)
wg.Add(1)
go square(&wg, ch, quit)
for i := 0; i < 10; i++ {
ch <- i * 2
}
quit <- true
wg.Wait()
}
------------ 아래 부터는 역할을 나누는 방법 ------------
자동차 공장에서 자동차를 만드는 예제
package main
import (
"fmt"
"sync"
"time"
)
type Car struct {
Body string
Tire string
Color string
}
var wg sync.WaitGroup
var startTime = time.Now()
func main() {
tireCh := make(chan *Car)
paintCh := make(chan *Car)
fmt.Printf("Start Factory \n")
wg.Add(3)
go MakeBody(tireCh) // 1. 고루틴 생성
go InstallTire(tireCh, paintCh)
go PaintCar(paintCh)
wg.Wait()
fmt.Println("Close the factory")
}
func MakeBody(tireCh chan *Car) { // 2. 차체 생산
tick := time.Tick(time.Second)
after := time.After(10 * time.Second)
for {
select {
case <-tick:
// Make a Body
car := &Car{}
car.Body = "Sports car"
tireCh <- car
case <-after: // 3. 10초 뒤 종료
close(tireCh)
wg.Done()
return
}
}
}
func InstallTire(tireCh chan *Car, paintCh chan *Car) { // 4. 바퀴 설치
for car := range tireCh {
// Make Tire
time.Sleep(time.Second)
car.Tire = "Winter tire"
paintCh <- car
}
wg.Done()
close(paintCh)
}
func PaintCar(paintCh chan *Car) { // 5. 도색
for car := range paintCh {
// Make a body
time.Sleep(time.Second)
car.Color = "Red"
duration := time.Now().Sub(startTime) // 6. 경과 시간 출력
fmt.Printf("%.2f Complete Car: %s %s %s\n",
duration.Seconds(), car.Body, car.Tire, car.Color)
}
wg.Done()
}
책 참조 : Tucker의 Go언어 프로그래밍
사이트 참조 (고루틴/채널/동기화/셀렉트) : https://judo0179.tistory.com/88
사이트 참조 (c++ select) : https://reakwon.tistory.com/117
'Basic Programming > Golang' 카테고리의 다른 글
Golang - 컨텍스트 (0) | 2023.09.16 |
---|---|
Golang - 슬라이스 정렬 (1) | 2023.07.13 |
Golang - 슬라이스 요소 삭제/추가 (0) | 2023.07.13 |
Golang - 슬라이스 복제 (0) | 2023.07.13 |
Golang - Slicing (0) | 2023.07.13 |