goroutine and channel

2020. 4. 1. 17:25golang/golang-grammar

goroutine이란

go routine은 함수 및 메소드를 다른 함수 및 메소드와 동시에 사용할 수 있게 해준다. go routine은 상당히 가벼운 thread이다. thread와 비교하면 만들고 사용하는데 적은 비용이든다. 그러므로 go application은 몇천개의 goroutine이 몇천개 동시에 돌아가고 있습니다. 

 

goroutine의 장점 

  • goroutine은 thread와 비교하여 적은 비용이 든다. goroutine은 몇 kb이며 goroutine은 thread에 비해 application의 요청에 따라 stack의 용량을 늘렸다 줄일 수 있다. thread의 경우 stack사이즈가 고정되어 있다.
  • goroutine들은 몇개의 os thread에 다중송신한다. 몇 천개의 goroutine으로 실행되는 프로그램은 오로지 하나의 thread로 이루어져있다. 만약 goroutine이 입력때문에 blocking되어 있다면 다른 os Thread를 만들어서 남아있는 goroutine을 새로 만든 thread에 옮겨버린다.
  • goroutine들 사이에는 channel을 통해 의사소통한다. channel은 의도적으로 goroutine을 사용하여 공유 메모리에 접근할 때 경쟁상태를 예방한다.

 

goroutine 사용해보기

일반적인 함수의 경우 아래의 코드와 같이 순차적으로 실행된다.

package main

import (
	"fmt"
)

func main() {
	human1 := [2]string{"sang", "hoon"}
	human2 := [2]string{"Hyun", "Jae"}

	humanName(human1)
	humanName(human2)

}

func humanName(names [2]string) {
	for _, name := range names {
		fmt.Println(name)

	}
}

// sang 
// hoon
// Hyun
// Jae

사진으로 보면 아래와 같이 순서대로 실행됩니다.

 

goroutine을 사용하면 다른 함수들을 동시에 같이 사용할 수 있다. 

package main

import (
	"fmt"
	"time"
)

func main() {
	human1 := [2]string{"sang", "hoon"}
	human2 := [2]string{"Hyun", "Jae"}

	go humanName(human1)
	humanName(human2)

}

func humanName(names [2]string) {
	for _, name := range names {
		fmt.Println(name)
		time.Sleep(time.Second)
	}
}
//Hyun
//sang
//Jae
//hoon

여기서 time.Sleep()을 사용하는 이유는 go keyword로 지정한 코드가 실행될 충분한 시간을 주는 것이다.

 

추가로 go로 지정된 함수 및 저기서는 main이 종료되게 되면 go keyword가 있어도 바로 종료되게 된다. 만약 go 만적힌 함수만 실행하게 된다면 아무 출력없이 바로 실행될 것입니다. 

 

다중 goroutine 사용해보기

package main

import (
	"fmt"
	"time"
)

func numbers() {
	for i := 1; i <= 5; i++ {
		time.Sleep(250 * time.Millisecond)
		fmt.Printf("%d ", i)
	}
}
func alphabets() {
	for i := 'a'; i <= 'e'; i++ {
		time.Sleep(400 * time.Millisecond)
		fmt.Printf("%c ", i)
	}
}
func main() {
	go numbers()
	go alphabets()
	time.Sleep(3000 * time.Millisecond)
	fmt.Println("main terminated")
}


// 1 a 2 3 b 4 c 5 d e main terminated  

 

numbers 함수와 alphabets 함수를 서로 다른 시간 간격으로 실행된다. 

 

Channel 

channel은 goroutine들 끼리 소통할 수 있게 해주는 매개체이다. channel을 통해 값을 보내면 받는 쪽에서 값이 올때까지 blocking이 걸린다. 그래서 time.Sleep 같은 것이 없어도 channel을 통해 값을 받을 수 있다.

 

channel 생성

channel := make( chan bool)

예제 코드

package main

import (
	"fmt"
	"reflect"
)

func isNumbers(numbers int, c chan bool) {
	if reflect.TypeOf(numbers).String() == "int" {
		c <- true
	} else {
		c <- false
	}

}

func main() {
	c := make(chan bool)
	numbers := [4]int{1, 1, 3, 2}
	for _, number := range numbers {
		go isNumbers(number, c)
		fmt.Println(<-c)
	}
}

c라는 channel을 만들어준 후 <-c 부분을 만나면 c가 값을 받을 때까지 blocking됩니다. 그래서 일반적인 goroutine과 달리 바로 종료되지 않습니다. 값을 넣을 때는 c <- true와 같이 넣어줍니다. 

 

함수에서 receiver를 다음과 같이 설정해주면 send 전용 channel을 만들 수 있다.

package main

import "fmt"

func sendData(sendch chan<- int) {  
    sendch <- 10
    fmt.Println(<- sendch) // Error
}

func main() {  
    chnl := make(chan int)
    go sendData(chnl)
    fmt.Println(<-chnl)
}
//10

 

또한 하나의 변수를 추가하여 성공적으로 receive했나 확인할 수 있다. 

 

close and range

더 이상 받을 값이 없는 경우 close를 receiver에게 알릴 수 있다. close를 통해 value의 상태 값들도 받을 수 있다.

v, ok := <- ch 

ok가 true면 성공적으로 값을 받은 것이고 false면 실패한 것이다. 혹은 range를 사용해 받은 만큼 값들을 반복문을 돌려 값을 이용할 수 있다.

package main

import (  
    "fmt"
)

func producer(chnl chan int) {  
    for i := 0; i < 10; i++ {
        chnl <- i
    }
    close(chnl)
}
func main() {  
    ch := make(chan int)
    go producer(ch)
    for v := range ch {
        fmt.Println("Received ",v)
    }
}