Golang Concurrency - Gorutine (2)

2022. 8. 10. 22:31개발/Golang

728x90
반응형

1장에서는 간단히 고루틴에대해 알아보았다. 2번째로는 select와 고루틴을 더욱 심화적으로 알아보고자 한다.

 

1. select 키워드

golang의 select는 채널에 대한 swtich문과 같다. 실제로 select를 이용해 고루틴이 여러 통신 연산을 기다리게 할 수 있다.

하지만 문제점도 있다. 큰 문제는 데드락 (deadlock) 이다. 따라서 프로세스를 설계하고 개발할 때 데드락이 발생하지 않도록 각별하게 신경써야한다!!

 

1-1. select example

package main

import (
	"fmt"
	"math/rand"
	"os"
	"strconv"
	"time"
)

func gen(min, max int, createNumber chan int, end chan bool) {
	for {
		select {
		case createNumber <- rand.Intn(max-min) + min:
		case <-end:
			close(end)
			return
		case <-time.After(4 * time.Second):
			fmt.Println("\ntime.After()!")
		}
	}
}

func main() {
	rand.Seed(time.Now().Unix())
	createNumber := make(chan int)
	end := make(chan bool)

	if len(os.Args) != 2 {
		fmt.Println("Please give me an integer")
		return
	}

	n, _ := strconv.Atoi(os.Args[1])
	fmt.Printf("Going to create %d random numbers.\n", n)
	go gen(0, 2*n, createNumber, end)

	for i := 0; i < n; i++ {
		fmt.Printf("%d ", <-createNumber)
	}

	time.Sleep(5 * time.Second)
	fmt.Println("Exiting....")
	end <- true
}

간단하게 살펴보자면 세 개의 case문으로 구분하였고 default는 세 번째가 default를 한다고 생각하면 된다.

select 문은 모든 채널을 동시에 확인하기 때문에 순차적으로 실행되지 않는다.

select 문은 있는 채널 중 사용 할 수 있는것이 없다면 사용 가능한 채널이 나타날때까지 블록된다. <- 처리를 못하면 deadlock이 걸리는이유!!!

 

그리고 마지막으로 end을 넣으므로 select문을 종료한다.

 

< 결과 >

# go run main.go 10  
Going to create 10 random numbers.
7 7 13 19 3 16 12 19 16 18
Exiting....

 

select의 가장 큰 장점은 여러 채널에 연결하거나 관리할 수 있을뿐 아니라 각 채널의 동작을 조율할수 있다는점!!!

채널은 고루틴에 연결돼 있기 때문에 select는 이렇게 고루틴에 연결된 채널을 연결한다. 따라서 select문은 Go의 동시성 모델에서 상당히 중요한 역활을 담당한다.

 

2. 고루틴 만료시키기

2-1. time.After을 통한 만료

package main

import (
	"fmt"
	"time"
)

func main() {
	c1 := make(chan string)
	go func() {
		time.Sleep(3 * time.Second)
		c1 <- "c1 ok"
	}()

	select {
	case res := <-c1:
		fmt.Println(res)
	case <-time.After(4 * time.Second):
		fmt.Println("timeout c1")
	}
}

 

첫번째 case를 통해 c2 채널에서 값을 받고 time.After 4초가 리턴되면 타임아웃이 발생하지 않는다. 한마디로 sleep이 적고 after가 더많다면 타임아웃 메세지를 받을 확률이 낮아지며 또한 반대로 sleep이 높고 after가 적다면 timeout이 발생할 것이다.

 

< 결과 >

# go run main.go
c1 ok

2-3. Sync Package을 통한 만료

1. gorutine이 들어간 func를 만든다. ( timeout )

2. 고루틴을 기다려줄 sync.Add와 마칠 sync.Done() 사용해본다.

 

package main

import (
	"fmt"
	"os"
	"strconv"
	"sync"
	"time"
)

func timeout(w *sync.WaitGroup, t time.Duration) bool {
	temp := make(chan int)
	go func() {
		time.Sleep(5 * time.Second)
		defer close(temp)
		// 고루틴 종료를 기다림
		w.Wait()
	}()

	select {
	case <-temp:
		return false
	case <-time.After(t):
		return true
	}
}

func main() {
	arguement := os.Args
	if len(arguement) != 2 {
		fmt.Println("Need a time duration")
		return
	}

	var w sync.WaitGroup
	// 고루틴을 기다릴 갯수
	w.Add(1)

	t, err := strconv.Atoi(arguement[1])
	if err != nil {
		fmt.Println(err)
		return
	}

	duration := time.Duration(int32(t)) * time.Millisecond
	fmt.Printf("Timeout period is %s\n", duration)

	if timeout(&w, duration) {
		fmt.Println("Timed out!")
	} else {
		fmt.Println("OK!")
	}

	// 첫번재 gorutine 종료
	w.Done()

	if timeout(&w, duration) {
		fmt.Println("Timed out!")
	} else {
		fmt.Println("OK!")
	}
}

< 결과 >

# go run main.go 10000
Timeout period is 10s
Timed out!
OK!

결과를 보면 처음 if문은 time.After()가 10초안에 발생하지만 그전에 temp가 리턴되어 false가 반환되고 time out!이 나타나게 된다. 그리고 sync.Done()이 있기에 고루틴을 종료할수 있다.

 

하지만 두번째 if문은 sync.Done()이 없다. 그렇기에 time.After()가 리턴되고 OK! 라는 메세지가 나오게 된다.

 

출저 : mastring go를 보고 정리한 내용입니다.

728x90
반응형