Golang - 채널과 컨텍스트

Lumi·2022년 2월 16일
0

Golang

목록 보기
24/38
post-thumbnail

🔥 채널과 컨텍스트

채널은 고루틴끼리 메세지를 전달할 수 있는 메세지 큐

채널을 생성할떄에는 make를 활용 합니다.

  • var message chan string = make(chan string)
  • 채널의 type은 chan입니다.

그후 채널에 데이터를 넣을떄에는 이처럼 사용합니다.

  • message <= "this is a message

반대로 데이터를 뺼 떄에는

  • var msg string = <- messages

부등호의 방향을 보면 쉽게 이해가 가능합니다!

예제1

package main

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

func main() {
	var wg sync.WaitGroup

	c := make(chan int)

	wg.Add(1)

	go square(&wg, c)

	c <- 9
	wg.Wait()
}

func square(wg *sync.WaitGroup, ch chan int) {
	n := <-ch

	time.Sleep(time.Second)

	fmt.Println(n * n)
	wg.Done()
}

이 코드를 보면 채널이라는 역할은 변수와 비슷한 역할을 하고 있다고 생각을 하였습니다.

  • 왜냐하면 값을 저장하고 뺴오는 작업이 가능하기 떄문에

보이는 바와 같이 단순히 값을 넣어준뒤 square함수를 안에서 안에 있는 값을 뺴오는 과정을 진행합니다.

이떄 값을 뺴오게 되면 채널에는 값이 없습니다.

  • 그러기 떄문에 또 뺴오면 에러를 발생시킵니다.
package main

import (
	"fmt"
	"sync"
)

func main() {
	var wg sync.WaitGroup

	c := make(chan int)

	wg.Add(1)

	go square(&wg, c)

	c <- 9
	// c <- 9
	wg.Wait()
}

func square(wg *sync.WaitGroup, ch chan int) {

	for i := 0; i < 2; i++ {
		n := <-ch

		fmt.Println(n * n)
	}

	wg.Done()
}

코드를 보면 채널에 값은 한개만 넣게 됩니다.

하지만 for문을 통해서 두개의 값을 뺴오고 있고 그러기 떄문에 이 코드는 에러를 발생 시키게 됩니다.

  • square가 채널에 값을 넣기 전에 먼저 작동을 하게 되는데도 채널에서 값이 나올수 있는 이유는 비동기적으로 프로그램을 실행시켰기 떄문입니다.

채널 크기

채널의 기본크기는 0 입니다.

앞서 테스트한 방식과 같은 방식으로 선언을 하면 0을 유지하고 이 방식은 썩 좋은 방식은 아닙니다.

쉽게 택배를 예로 들어 보겠습니다.

기본 크기가 0이라는 소리는 누군가 데이터를 넣어줄떄까지 기다려야 한다는 소리입니다.

그러기 댸문에 누군가 도착(이는 개발자의 코드를 의미)하게 되면 데이터를 가지게 됩니다.

반면에 크기가 0이 아니라는 소리는 그냥 값을 넣어주면 된다는 의미입니다.

--

좀 말이 비슷한데 택배기사님을 예로들면

보관함이 없으면 누군가 받으로 올떄까지 기다려야 하지만

보관함이 있으면 그냥 물품을 놓고 가면 된다는 것과 비슷합니다.

크기를 부여하는 방법은 매우 간단합니다.

package main

import (
	"fmt"
	"time"
)

func main() {
	// ch := make(chan int, 2)
     ch := make(chan int)

	go square()
	ch <- 9

	fmt.Println("Never print")
}

func square() {
	for {
		time.Sleep(2 * time.Second)
		fmt.Println("sleep")
	}
}
  • 아직은 goroutine와 채널에 대해서 정확하게 이해를 한 것이 아니라서 이게 설명이 될지는 모르겠습니다..

일단 크기를 지정하지 않았다면 채널에 들어오는 값은 있지만 나가는 값이 없기 떄문에 main함수가 종료가 되지가 않습니다.

  • 누군가 값을 가져가 주어야 종료가 됩니다.
  • 이는 택배 기사님이 사용자가 올떄까지 물품을 계속 들고 있는것과 같습니다.

하지만 주석처리한 부분을 통해서 크기를 정해주면 Never print가 작동을 하면서 프로그램이 종료가 됩니다.

  • 택배 기사님이 보관함에 물품을 놓고 가기만 하면 되는 것과 같습니다.

square함수가 실행이 되어도 2초간 기다리는 저 코드는 작동을 하기 전에 main()함수가 종료가 되기 떄문입니다.

채널에서 데이터 대기

package main

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

var wg sync.WaitGroup

func main() {
	ch := make(chan int)

	wg.Add(1)
	go square(&wg, ch)

	for i := 0; i < 10; i++ {
		ch <- i * 2
	}
	wg.Wait()
	fmt.Println("Never print")
}
func square(wg *sync.WaitGroup, ch chan int) {
	for n := range ch {
		fmt.Println(n * n)
		time.Sleep(time.Second)
	}
	wg.Done()
}

채널 또한 range를 통해서 데이터를 뽑아 올수 있습니다.

작동 방식은 매우 간단합니다.

기본적으로 main함수 내에서 채널에 데이터를 for문을 통해서 넣어 주게 됩니다.

그러기 때문에 square부분에서는 range를 통해서 값을 뽑아오고 있는 상황이고요

이게 처음에는 정상적으로 작동을 하지만 마지막에는 에러를 발생 시킵니다.

  • all goroutines are asleep - deadlock!

이 에러가 발생하는 이유는 이제 채널에 데이터가 없기 떄문입니다.

하지만 square안에 있는 For문에서는 계속해서 데이터를 뽑아 오려고 해서 문제가 발생을 하는 것이지 심각하고 복잡한 에러는 아닙니다.

해결을 하기 위해서는 Close()를 통해서 채널을 닫아주면 됩니다.

  • 이 부분은 Youtube에서 다루었습니다.

select 문

switch와 비슷합니다.

이 문법은 여러 채널에서 동시에 데이터를 기다릴떄 사용을 하게 됩니다.

package main

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

var wg sync.WaitGroup

func main() {
	ch := make(chan int)

	wg.Add(1)
	go square(&wg, ch)

	for i := 0; i < 10; i++ {
		ch <- i * 2
	}
	wg.Wait()
	fmt.Println("Never print")
}

func square(wg *sync.WaitGroup, ch chan int) {
	tick := time.Tick(time.Second)
	tenminute := time.After(10 * time.Second)

	for {
		select {
		case <-tick:
			fmt.Println("Tick")
		case <-tenminute:
			fmt.Println("temminute")
			wg.Done()
			return
		case n := <-ch:
			fmt.Println(n * n)
			time.Sleep(time.Second)
		}
        // 랜덤하게 실행이 된다.
	}
}
  • 이 부분은 Youtube에서 정리를 해 놓았습니다.

채널로 생산자/소비자 패턴 구현

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)
	go InstallTire(tireCh, paintCh)
	go PaintCar(paintCh)

	wg.Wait()
	fmt.Println("Close Factory")
}

func MakeBody(tirech chan *Car) {
	tick := time.Tick(time.Second)
	after := time.After(10 * time.Second)

	for {
		select {
		case <-tick:
			car := &Car{Body: "Sports car"}
			tirech <- car
		case <-after:
			close(tirech)
			wg.Done()
			return
		}
	}
}

func InstallTire(tirech, painch chan *Car) {
	for car := range tirech {
		time.Sleep(time.Second)
		car.Tire = "Winter tire"
		painch <- car
	}
	wg.Done()
	close(painch)
}

func PaintCar(paintCh chan *Car) {
	for car := range paintCh {
		time.Sleep(time.Second)
		car.Color = "Red"
		duration := time.Now().Sub(startTime)
		fmt.Println(duration)
	}
	wg.Done()
}
  • 이 부분 또한 youtube에 기록을 하였습니다.
  • 글로 적기에는 너무 길어서;;

컨텍스트

작업을 지시할 떄 작업 가능 시간, 작업 취소 등의 조건을 지시할 수 있는 작업 명세서 역할을 하게 됩니다.

package main

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

var wg sync.WaitGroup

func main() {
	wg.Add(1)
	ctx, cancel := context.WithCancel(context.Background())
	// 기본 컨텍스트를 반해 줍니다.
	go PrintEverySecond(ctx)
	time.Sleep(5 * time.Second)
	cancel()
	wg.Wait()
}

func PrintEverySecond(ctx context.Context) {
	tick := time.Tick(time.Second)

	for {
		select {
		case <-ctx.Done():
			wg.Done()
			return
		case <-tick:
			fmt.Println("tick!")
		}
	}
}
  • 이전에 작성했던 예시를 다른 방식으로도 적용 가능하다는 것을 보여주는 코드 입니다.
  • Youtube를 통해서 정리 하였습니다.
package main

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

var wg sync.WaitGroup

func main() {
	wg.Add(1)
	ctx := context.WithValue(context.Background(), "number", 9)
	// 기본 컨텍스트를 반해 줍니다.
	go PrintEverySecond(ctx)

	wg.Wait()
}

func PrintEverySecond(ctx context.Context) {

	if v := ctx.Value("number"); v != nil {
		n := v.(int)
		fmt.Println(n * n)
	}
	wg.Done()
}
  • 다른 방법으로는 WithValue를 통해서 데이터또한 저장이 가능합니다.
  • 사실 그냥 PrintEverySecond(9)를 넣은 값과 동일하게 작동을 하게 됩니다 ..ㅎ
profile
[기술 블로그가 아닌 하루하루 기록용 블로그]

0개의 댓글