728x90

요약

- 채널 : 고루틴 끼리 메시지를 전달하는 메시지 큐

- 셀렉트 : 여러개의 채널을 동시에 기다리는 구문

 

메시지 큐

 - 윈도우 프로그래밍을 하면서 경험했던 윈도우 메시지 큐와 동일한 개념이다

 - 메시지(다양한 정보)를 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

 

[리눅스] 다중입출력 - select개념과 설명과 예제

select, poll, epoll과 같이 더 많은 정보와 예제를 담은 리눅스 교재를 배포했습니다. 아래의 페이지에서 리눅스 교재를 받아가세요. https://reakwon.tistory.com/233 리눅스 프로그래밍 note 배포 티스토리에

reakwon.tistory.com

 

728x90

'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

+ Recent posts