{Go} 14. go-routine / 15. channel

Jerry·2021년 6월 20일
0

14. go routine

  • 타 언어의 스레드와 유사함
  • 타 언어의 스레드 보다 생성이 간단하단 장점이 있음
  • 자바의 경우 하나의 스레드 생성시 2MB의 메모리가 소요되지만 고루틴의 경우 1KB 소요
    => 동시에 더 많은 고루틴을 생성하여 작업 가능
  • 공유메모리 사용 시 정확한 동기화 필요
  • 채널을 통해 고루틴 간 통신 가능

기본 사용

func ex1() {
	fmt.Println("exe1 func start", time.Now())
	time.Sleep(1 * time.Second)
	fmt.Println("exe1 func end", time.Now())
}

func main(){
	fmt.Println("main routine start", time.Now())
    // 실행시킬 subroutine에 go 만 앞에 붙여주면 됨
	go ex1()                   // go routine : sub-routine
    fmt.Println("main routine end", time.Now())
}

멀티 코어를 최대한 활용

// runtime.NumCPU() : CPU 갯수 구함
// runtime.GOMAXPROCS: 구한 CPU 갯수를 GO처리에 사용할 최대 CPU 갯수로 할당
runtime.GOMAXPROCS(runtime.NumCPU())   
// runtime.GOMAXPROCS 인자로 전달된 cpu 갯수를 셋팅하고 이전 값을 반환. 인자값이 < 1 인 경우, 이전 설정을 그대로 사용
fmt.Println("current system CPU :", runtime.GOMAXPROCS(0)) // 설정값 출력

고루틴 - 클로저 사용

고루틴 클로저는 반복문 종료 후 실행되므로 인자 전달에 유의해야한다.



s := "closure test"
for i := 0; i < 1000; i++ {
   // 즉시 실행 함수로 인자를 넘기지 않으면
   // 반복문이 끝난 시점에 i가 999가 된 상태 값이 전달되어 999만 999번 생성된다.
	go func() {
		fmt.Println(s, i, " - ", time.Now())
	}()  
    
   // 인자로 전달한 시점에 i가 값 복사 되므로 나중에 실행되도 문제 없음
	go func(n int) {
		fmt.Println(s, n, " - ", time.Now())
	}(i)  
}

WaitGroup

  • main routine에서 생성한 sub go-routine 들은 main routine이 종료되면 같이 종료된다.
  • sub routine이 모두 종료 되고 main이 종료되기 위해서 Sleep을 해도 되지만 정확한 타이밍을 잡기 어렵다.
  • WaitGroup을 사용하면 해결된다.
wg := new(sync.WaitGroup)

for i := 0; i < 100; i++ {
	wg.Add(1)
	go func(n int) {
		fmt.Println("wait Group : ", n)
		wg.Done()
	}(i)
}

// Add == Done 횟수 같아야 함
wg.Wait()
fmt.Println("WaitGroup end!")

15.channel : 채널

  • 채널은 reference type(참조형)
  • 고루틴 간의 데이터 교환 및 실행흐름 동기화를 위해 사용
  • 데이터 전달 자료형 선언 후 사용(지정된 타입만 교류 가능)
    => interface{} 를 이용해서 자료형 상관없이도 송수신 가능

<- : 수신 (변수 <- 채널)
-> : 송신 (데이터 -> 채널)

기본 선언


func work1(v chan int) {
	fmt.Println("work1 : s --> ", time.Now())
	time.Sleep(1 * time.Second)
	fmt.Println("work1 : E --> ", time.Now())

	v <- 1
}

func work2(v chan int) {
	fmt.Println("work2 : s --> ", time.Now())
	time.Sleep(1 * time.Second)
	fmt.Println("work2 : E --> ", time.Now())

	v <- 2
}

v := make(chan int) // int형 채널 선언. 채널은 참조형

go work1(v)
go work2(v)

// 먼저 수신된 순서대로 출력됨. 하나 송신되면 하나 수신 할수있음
fmt.Println(<-v)
fmt.Println(<-v)

// 수신할 데이터가 없는데 수신을 기다리면 데드락 발생
// fmt.Println(<-v)

채널 버퍼 사용

채널의 버퍼 용량이 full이 될 때가지 기다렸다가 가득 차면 한꺼번에 내보냄

// channel의 용량을 3로 설정
// 3개 보낸 후 한꺼번에 3개 받기
ch := make(chan bool, 3) 

채널 close

  • close : 채널 닫기
    닫힌 채널에 값 전송 시 패닉(예외) 발생
  • range : 채널 안에서 순회하며 값을 꺼낸다.
    채널을 닫아야 반복문이 종료된다. (채널이 열려있고 값전송 없으면 무한대기)
ch := make(chan bool)
go func() {
	for i := 0; i < 5; i++ {
		ch <- true
	}
	close(ch) // 5회 채널에 값 전송 후 채널 닫기
}()

// 채널에서 값을 꺼내온다.
// 채널이 close 될 때 까 계속 대기. close가 되지 않으면 무한 대기
for i := range ch { 
	fmt.Println("ex 1 :", i)
}
  • 채널 close 유무 확인
ch := make(chan string)

go func() {
	for i := 0; i < 2; i++ {
		ch <- "go"
	}
}()

val1, ok1 := <-ch
fmt.Println("ex1 : ", val1, ok1)
val2, ok2 := <-ch
fmt.Println("ex1 : ", val2, ok2)
close(ch)

val3, ok3 := <-ch
// 채널 수신 시 받는 두번째 인자인 ok3 값으로 채널 닫힘 유무를 알수있음
fmt.Println(val3, ok3) 

발신전용 / 수신전용 채널

func sendOnly(c chan<- int, cnt int) {
	for i := 0; i < cnt; i++ {
		c <- i
	}
	c <- 777
    // 입력받은 파라미터 c는 송신 전용 채널이기 때문에 수신 불가
	//fmt.Println(<-c) 
}

func receiveOnly(c <-chan int) {
	for i := range c {
		fmt.Println("received : ", i)
	}
	fmt.Println(<-c)
}

c := make(chan int)

go sendOnly(c, 10) // 송신전용 (데이터 -> 채널)
go receiveOnly(c)  // 수신전용 (데이터 <- 채널)

채널 샐렉트

  • 채널에 값이 수신되면 해당 case 문 쉴행
  • select 문에 default 사용 유의
    => 데이터가 비동기로 전달되므로 수신 전에 default로 빠져버림
ch1 := make(chan int)
ch2 := make(chan string)

go func() {
	for {
		ch1 <- 77
		time.Sleep(250 * time.Millisecond)
	}
}()

go func() {
	for {
		ch2 <- "go hi~"
		time.Sleep(250 * time.Millisecond)
	}
}()

go func() {
	for {
		select {
		case num := <-ch1:
			fmt.Println("ch1 : ", num)
		case str := <-ch2:
			fmt.Println("ch2 : ", str)
			// default:
			// 	fmt.Println("default")
		}
	}
}()

참고자료

[학습 자료] 인프런 - 쉽고 빠르게 끝내는 GO언어 프로그래밍 핵심 기초 입문 과정
[공식사이트] https://golang.org/tutorial

profile
제리하이웨이

0개의 댓글