백엔드, 기타/Golang

Golang 4. 고루틴

데브힐러 2020. 8. 23. 10:15
반응형

1. 고루틴

 

 #예제 : 고루틴 100개를 동시에 실행하면서 각 고루틴이 랜덤한 시간동안 대기하는 go함수 작성

import (
	"fmt"
	"math/rand"
	"time"
)
func hello(n int) {
	r := rand.Intn(100) //랜덤한 숫자 생성
	time.Sleep(time.Duration(r)) //랜덤한 시간 동안 대기
	fmt.Println(n)
}

func main() {
	for i:=0; i<100; i++ {
		go hello(i)
	}
	fmt.Scanln()
}

 

 

 # 클로저를 고루틴으로 실행하기

func main(){
	runtime.GOMAXPROCS(1) // CPU를 하나만 사용
	s := "Hello, World"
	for i:=0; i<100; i++ {
		go func(n int) { //익명 함수를 고루틴으로 실행(클로저)
			fmt.Println(s, n) //s와 매개변수 받은 n값 출력
		}(i) //반복문의 변수는 매개변수로 넘겨줌
	}
	fmt.Scanln()
}

 클로저를 고루틴으로 실행할 때 반복문 안에서 변수 사용에 주의해야 한다. 예제에서는 반복문으로 증가하는 i를 클로저에서 그대로 사용하지 않고, 매개변수로 넘겨주었다.

 

 일반 클로저는 반복문 안에서 순서대로 실행되지만 고루틴으로 실행한 클로저는 반복문이 끝난 뒤에 고루틴이 실행된다.

 

 ● 클로저를 고루틴으로 실행할 때 반복문에 의해 바뀌는 변수는 반드시 매개변수로 넘겨준다. 즉 매개변수로 넘겨주는 시점에 해당 변수의 값이 복사되므로 고루틴이 생성될 때 그대로 사용할 수 있다. 또한, CPU코어를 하나만 사용하든 여러 개 사용하든 상관없이 반복문에 의해 바뀌는 변수는 매개변수로 넘겨주어야 한다.

 

 

 

2. 채널

 

 ● 채널(channel)은 고루틴끼리 데이터를 주고받고, 실행 흐름을 제어하는 기능이다. 채널 자체는 값이 아닌 레퍼런스 타입이다.

 # make(chan 자료형)

func sum(a int, b int, c chan int) {
	c <- a+b
}

func main() {
	/*var c chan int
	c = make(chan int)*/
	
	c := make(chan int) //int형 채널 생성
	go sum(1,2,c) //sum을 고루틴으로 실행한 뒤 채널을 매개변수로 넘겨 줌
	n := <- c //채널에서 값을 꺼낸 뒤 n에 대입
	fmt.Println(n)
}

 

 

 

 # 동기 채녈

func main() {
	done := make(chan bool)
	count := 3

	go func() {
		for i:=0; i<count; i++ {
			done <- true
			fmt.Println("고루틴 : ", i)
			time.Sleep(2 * time.Second)
		}
	}()

	for i:=0; i<count; i++ {
		<-done
		fmt.Println("메인함수",i )
	}
}

 ● make변수에 매개변수를 하나만 지정했으므로 동기 채널이 생성된다.

 

 

 

 # 채널 버퍼링, 비동기 채널

 make(chan 자료형, 버퍼개수)

 ● 채널에 버퍼를 1개 이상 설정하면 비동기 채널이 생성된다. 비동기 채널은 보내는 쪽에서 버퍼가 가득차면 실행을 멈추고 대기하며, 받는 쪽에서는 버퍼에 값이 없으면 대기한다.

 

func main() {
	runtime.GOMAXPROCS(1)
	done := make(chan bool, 2) //버퍼가 2개인 비동기 채널 생성
	count := 4

	go func() {
		for i:=0; i<count; i++ {
			done <- true
		//채널에 true를 보냄, 버퍼가 가득차면 대기
			fmt.Println("고루틴 : ", i)
		}
	}()

	for i:=0; i<count; i++ {
		<-done //버퍼에 값이 없으면 대기, 값을 꺼냄
		fmt.Println("메인함수 : ", i)
	}
}

 

 

 # 보내기 전용, 받기 전용 채널

func producer(c chan<- int){ //보내기 전용 채널
	for i:=0; i<5; i++ {
		c <-i
	}
	c<-100 //채널에 값을 보냄
	//fmt.Println(<-c) //채널에서 값을 꺼내면 컴파일 에러
}

func consumer(c <-chan int) { // 받기 전용 채널
	for i := range c {
		fmt.Println(i)
	}
	fmt.Println(<-c) 
    //c<-1 //채널에 값을 보내면 컴파일 에러
}

func main() {
	c := make(chan int)
	go producer(c)
	go consumer(c)
	fmt.Scanln()
}

 

 -- 출력

0
1
2
3
4
100

 

 

 # 채널만 사용하여 값 더하기

func num(a, b int) <-chan int {
	out := make(chan int)

	go func() {
		out <- a //채널에 a의 값을 보냄
		out <- b //채널에 b의 값을 보냄
		close(out)
	}()
	return out
}

func sum(c <-chan int) <-chan int {
	out := make(chan int)

	go func() {
		r := 0
		for i := range c { //range를 사용하여 채널이 닫힐 때까지 값을 꺼냄
			r = r + i
		}
		out <- r
	}()
	return out
}

func main() {
	c := num(1, 2) //1과 2가 들어있는 채널이 리턴됨
	out := sum(c) // 채널 c를 매개변수에 넘겨서 모두 더함
	//더한 값이 들어있는 out 채널을 리턴
	fmt.Println(<-out) // 3
}

 

 

 # select문

func main() {
	c1 := make(chan int)
	c2 := make(chan string)

	go func(){
		for {
			c1 <- 10
			time.Sleep(100*time.Millisecond)
		}
	}()
	go func(){
		for {
			c2 <- "hello world"
			time.Sleep(500*time.Millisecond)
		}
	}()
	go func() {
		for {
			select {
			case i := <-c1:
			// 채널 c1에 값이 들어왔다면 값을 꺼내서 i에 대입
				fmt.Println("c1: ", i)
			case s := <-c2:
				fmt.Println("c2: ", s)
            case <-time.After(50*time.Millisecond):
				fmt.Println("timeout")
			}
		}
	}()
	time.Sleep(10*time.Second)
}

 

 

 

 

 

반응형