Go 동시성 패턴 1

동시성이란 무엇입니까?

동시성은 독립적으로 실행되는 계산의 구성입니다.

동시성은 독립적으로 실행 가능한 작업의 조합입니다.

병렬 처리가 아닙니다.

보링 기능

func main() {
	boring("boring!
") } func boring(msg string) { for i := 0; i < 10; i++ { fmt.Println(msg, i) time.Sleep(time.Duration(rand.Intn(1e3)) * time.Millisecond) } }
boring!
0 boring!
1 boring!
2 boring!
3 boring!
4 boring!
5 boring!
6 boring!
7 boring!
8 boring!
9

메시지는 0초에서 1초 사이의 임의의 간격으로 출력됩니다.

Go는 고루틴을 사용하여 동시성을 구현합니다.

그러나 main 함수가 종료되면 고루틴도 종료됩니다.

func main() {
	go boring("boring!
")//바로 종료되어 버림 } func boring(msg string) { for i := 0; i < 10; i++ { fmt.Println(msg, i) time.Sleep(time.Duration(rand.Intn(1e3)) * time.Millisecond) } }

위의 코드에서는 Boring 함수 직후에 종료되기 때문에 메시지가 출력되지 않습니다.

고루틴

고루틴은 go 문에 의해 독립적으로 실행되는 함수입니다.

아주 가벼워서 수천개 써도 상관없어요. 하나의 스레드에서 수천 개의 고루틴을 실행하는 것은 중요하지 않습니다.

의사소통

func main() {
	go boring("boring!
") fmt.Println("I am listening") time.Sleep(time.Second * 2) fmt.Println("you are boring. I am leaving.") } func boring(msg string) { for i := 0; i < 10; i++ { fmt.Println(msg, i) time.Sleep(time.Duration(rand.Intn(1e3)) * time.Millisecond) } }

여기서 지루한 기능은 다른 고루틴과 대화하는 것이 아닙니다.

그냥 메시지를 보내

채널/동기화

채널은 두 고루틴 간의 대화를 허용합니다.

또한, 채널을 사용하여 통신할 경우 정보가 처리될 때까지 기다리십시오.

func main() {
	c := make(chan string)
	go boring("boring!
", c) for i := 0; i < 5; i++ { fmt.Printf("You say %q\n", <-c) }//for 루프를 기다린다.

fmt.Println("I am listening") time.Sleep(time.Second * 2) fmt.Println("you are boring. I am leaving.") } func boring(msg string, c chan string) { for i := 0; ; i++ { c <- fmt.Sprintf("%s %d", msg, i) time.Sleep(time.Duration(rand.Intn(1e3)) * time.Millisecond) } }

그러나 버퍼를 사용하면 동기화가 제거됩니다.

패턴

Generator: 채널을 반환하는 functino

func main() {

	fmt.Println("I am listening")
	msg := boring("boring!
") for i := 0; i < 5; i++ { fmt.Printf("%q\n", <-msg) } fmt.Println("you are boring. I am leaving.") } func boring(msg string) <-chan string {//return receive only channel c := make(chan string) go func() { for i := 0; ; i++ { c <- fmt.Sprintf("%s %d", msg, i) time.Sleep(time.Duration(rand.Intn(1e3)) * time.Millisecond) } }() return c }

이전과 동일하게 작동하지만 더 깔끔한 코드로 작동합니다.

여러 인스턴스를 쉽게 만들 수도 있습니다.

func main() {

	fmt.Println("I am listening")
	jon := boring("jon:")
	ann := boring("ann:")
	for i := 0; i < 5; i++ {
		fmt.Printf("%q\n", <-jon)
		fmt.Printf("%q\n", <-ann)
	}
	fmt.Println("you are boring. I am leaving.")
}

func boring(msg string) <-chan string {
	c := make(chan string)
	go func() {
		for i := 0; ; i++ {
			c <- fmt.Sprintf("%s %d", msg, i)
			time.Sleep(time.Duration(rand.Intn(1e3)) * time.Millisecond)
		}
	}()
	return c
}

위와 같이 쓰면 jon과 ann이 서버로부터 응답을 받는 것과 같다.

그러나 현재 코드에서는 채널의 동기화 특성 때문에 jon과 ann을 모두 준비해야 합니다.

멀티플렉싱

func main() {
	fmt.Println("I am listening")
	c := fanIn(boring("jon:"), boring("ann:"))
	for i := 0; i < 10; i++ {
		fmt.Printf("%q\n", <-c)
	}
	fmt.Println("you are boring. I am leaving.")
}

func boring(msg string) <-chan string {
	c := make(chan string)
	go func() {
		for i := 0; ; i++ {
			c <- fmt.Sprintf("%s %d", msg, i)
			time.Sleep(time.Duration(rand.Intn(1e3)) * time.Millisecond)
		}
	}()
	return c
}

func fanIn(jon, ann <-chan string) <-chan string {
	c := make(chan string)
	go func() {
		for {
			c <- <-jon
		}
	}()
	go func() {
		for {
			c <- <-ann
		}
	}()
	return c
}

다중화를 팬인 기능이라고 합니다.

jon과 ann이 새 채널에 메시지를 보내면 새 채널이 기본 기능으로 인쇄됩니다.


이를 통해 코드 흐름에서 동기화를 유지하면서 두 채널을 분리할 수 있습니다.

시퀀싱 복원

다시 메시지를 순서대로 출력할 수 있습니다.

하지만 이번에는 지루한 함수가 주 함수와 대화하도록 새로운 구조체를 만들 수 있습니다.

func main() {
	fmt.Println("I am listening")
	c := fanIn(boring("jon:"), boring("ann:"))
	for i := 0; i < 10; i++ {
		msg1 := <-c
		fmt.Println(msg1.str)
		msg2 := <-c
		fmt.Println(msg2.str)
		msg1.wait <- true//wait 채널을 만들어서 boring 함수와 동기화 시킨다.

msg2.wait <- true } fmt.Println("you are boring. I am leaving.") } type message struct { str string wait chan bool } func boring(msg string) <-chan message { c := make(chan message) waitforit := make(chan bool) go func() { for i := 0; ; i++ { c <- message{fmt.Sprintf("%s %d", msg, i), waitforit} time.Sleep(time.Duration(rand.Intn(1e3)) * time.Millisecond) <-waitforit // main함수에서 받을 때 까지 기다린다.

} }() return c } func fanIn(jon, ann <-chan message) <-chan message { c := make(chan message) go func() { for { c <- <-jon } }() go func() { for { c <- <-ann } }() return c }

위의 코드에서 연결된 채널을 그리면,


위와 같이 Boring함수에서 2개의 채널을 생성하였으므로 2개의 채널을 가지게 됩니다.

보링 함수는 실행을 종료하기 위해 메인 함수로부터 waitforit 채널을 받아야 합니다.

이 기능 때문에 msg1과 msg2가 main 함수의 for 루프에서 서로 다른 보링 함수의 채널임을 알 수 있습니다.

msg1이 jon인지 ann인지는 확실하지 않지만 msg1과 msg2는 모두 jon이 될 수 없습니다.

jon이 새 메시지를 보내려면 지루한 함수의 for 루프가 다시 시작되어 waitforit를 기다려야 하기 때문입니다.


보링(“jon”)과 보링(“ann”)의 실행 속도는 time.Sleep 함수에 따라 결정되며 msg1과 msg2는 time.Sleep의 인수가 랜덤이기 때문에 변경될 수 있습니다.

실제로 프로그램을 실행해보면 jon과 ann이 바뀌는 것을 볼 수 있다.

jon: 0
ann: 0
jon: 1
ann: 1
ann: 2// 순서가 바뀜
jon: 2
ann: 3
jon: 3

선택하다

현재 채널의 상태에 따라 실행이 다를 수 있습니다.

Go가 라이브러리에서 동시성을 구축하지 않은 이유는 select 문을 만들기 위해서입니다.

func main() {
	c1 := gen()
	c2 := gen()

	select {// 통신 가능할 때 까지 기다림, 둘 중 하나라도 연결되면 select 문 종료
	case v1 := <-c1:
		fmt.Println("c1 was fast", v1)
	case v2 := <-c2:
		fmt.Println("c2 was fast", v2)
	}
}

func gen() <-chan bool {
	c := make(chan bool)
	go func() {
		time.Sleep(time.Duration(rand.Intn(1e3)) * time.Millisecond)
		c <- true
	}()
	return c
}

각각의 경우에는 평가 공식이 없으며 커뮤니케이션 채널이 있습니다.

select 문에서는 각각의 경우 통신 가능 여부를 평가한 후 통신 가능한 경우가 나올 때까지 기다립니다.

select 문을 사용하면 위에서 작성한 fanIn 함수를 하나의 고루틴으로 작성할 수 있습니다.

func fanIn(jon, ann <-chan message) <-chan message {
	c := make(chan message)
	go func() {
		for {
			select {
			case j := <-jon:
				c <- j
			case a := <-ann:
				c <- a
			}
		}
	}()

	return c
}

선택을 사용한 시간 초과

c := boring("jon:")
	for {
		select {
		case msg := <-c:
			fmt.Printf("%s \n", msg.str)
		case <-time.After(time.Millisecond * 500):
			fmt.Println("too slow")
			return
		}
	}

보링 기능이 0.5초 이상 걸리면 종료되는 기능. 이렇게 작성하면 각 채널 c가 0.5초 이상 걸리지만 시간이 걸리는지 확인할 수 있습니다.

루프를 벗어난 후 전체 루프에 대한 타이머를 설정합니다.

	c := boring("jon:")
	timeout := time.After(time.Second * 2)
	for {
		select {
		case msg := <-c:
			fmt.Printf("%s \n", msg.str)
		case <-timeout:
			fmt.Println("too slow")
			return
		}
	}

이렇게 작성하면 전체 for 루프가 2초 걸립니다.