~61까지 살펴보았고 이전 내용 살펴보기
이제는 62부터 살펴보자! Go 언어에서 동시성은 중요한 기능이라고 설명했는데, 어떤 기능이 있을까?

동시성

나는 이미 Concurrency에 대해서 이해를 하고 있는데,
조금더 디테일하게 알고싶으면 golang codewalk 를 참고

Goroutines

_고루틴은 Go 런타임에 의해 관리되는 경량 쓰레드이다.

go f(x, y, z) 의 코드는 새로운 고루틴을 시작시킨다.

현재의 고루틴에서는 f, x, y, z가 평가(evaluation) 되고,
새로운 고루틴 f가 수행(exeuction) 된다.

고루틴은 동일한 주소 공간에서 실행되므로, 공유되는 자원으로의 접근은 반드시 동기화가 되어야 한다.
golang sync에서 유용한 기본 기능을 제공하고 있다.

package main

import (
    "fmt"
    "time"
)

func say(s string) {
    for i := 0; i < 5; i++ {
        time.Sleep(100 * time.Millisecond)
        fmt.Println(s)
    }
}

func main() {
    go say("world")
    say("hello")
}

위 결과를 실행하면 hello, world가 번갈아가면서 수행이 된다.

Channels

채널은 채널 연산자 <-를 이용해 값을 주고 받을 수 있는 타입이 존재하는 파이프이다.
(데이터는 화살표 방향으로 흐른다.)

ch <- v // v를 ch로 보낸다.
v := <- ch // ch로부터 값을 받아서 v로 넘긴다. 

맵이나 슬라이스처럼 채널은 사용되기 전에 생성되어야 한다.

ch := make(chan int)

기본적으로 송/수신은 상대편이 준비될 때까지 블록된다.
이런 특성이 고루틴이 명시적인 락이나 조건 없이 동기화 될 수 있도록 돕는다.

아래 예제에서는 sumc로 보내고, c로 부터 받은 값을 x,y에 넘겨준다.

package main

import "fmt"

func sum(a []int, c chan int) {
    sum := 0
    for _, v := range a {
        sum += v
    }
    c <- sum // send sum to c
}

func main() {
    a := []int{7, 2, 8, -9, 4, 0}

    c := make(chan int)
    go sum(a[:len(a)/2], c)
    go sum(a[len(a)/2:], c)
    x, y := <-c, <-c // receive from c

    fmt.Println(x, y, x+y)
}

동시성을 지원하기 때문에 채널이 필요한건가...
위에 예제에서는 a라는 리스트의 모든 값을 더하는 과정인데,
두개의 고루틴을 이용해서 합을하고, 마지막에 두 값을 합치는 예제이다.
각각의 스레드에서의 결과는 c에 저장되고 종료되면 x, y로 가니 최종적으로 x+y를 계산
일단 사용성만 이해하는걸로라고 하고 넘어가려고 하니

65에서 버퍼링되는 채널의 주제가 나왔다.
채널이 버퍼링 될수가 있다고 하는데,

ch := make(chan int, 100) 의 용량을 지정할 수 있는데,
용량만큼 버퍼링되는 채널을 생성할 수 있다.
버퍼링되는 채널로의 송신은 버퍼가 꽉 찰 때까지 블록된다.
수신측은 버퍼가 빌 때 블록이된다.
언제 어떻게 써야하는지는 모르겠음 일단 버퍼 용량을 넣을 수 있다는 정도로 이해
웹에서 UI 업데이트를 동시에 하기 위해서 사용하려나 모르겠음.

Range와 Close

데이터 송신측은 더이상 보낼 값이 없다는 것을 알리기 위해 채널을 close할 수 있다.
수신측에서는 v, ok := <-ch 다음과 같이 두번째 인자를 줌으로써 채널이 닫혔는지 테스트 가능
채널이 이미 닫혔으면 false

주의 송신측만 채널을 닫을 수 있다. 수신측에서는 불가능 (이미 닫힌 채널에 데이터를 보내면 응?)
채널은 파일과 다르다. 항상 닫을 필요가 없다. range의 루프를 종료시키는 느낌과 유사함
더이상 값을 보낼게 없다고 말할때

package main

import (
    "fmt"
)

func fibonacci(n int, c chan int) {
    x, y := 0, 1
    for i := 0; i < n; i++ {
        c <- x
        x, y = y, x+y
    }
    close(c) // channel을 닫음
}

func main() {
    c := make(chan int, 10)
    go fibonacci(cap(c), c)
    for i := range c {
        fmt.Println(i)
    }
}

Select

고루틴이 다수의 통신 동작으로부터 수행 준비를 기다릴 수 있게 한다.
selectcase구문으로 받은 통신 동작들 중 하나가 수행될 수 있을때까지 수행을 블록
다수의 채널이 동시에 준비되면 그 중 하나를 무작위로 선택

package main

import "fmt"

func fibonacci(c, quit chan int) {
    x, y := 0, 1
    for {
        select {
        case c <- x:
            x, y = y, x+y
        case <-quit:
            fmt.Println("quit")
            return
        }
    }
}

func main() {
    c := make(chan int)
    quit := make(chan int)
    go func() {
        for i := 0; i < 10; i++ {
            fmt.Println(<-c)
        }
        quit <- 0
    }()
    fibonacci(c, quit)
}

select, case를 같이 쓰는 예제인것 같은데
이것도 어디에 어떻게 쓰일지... 감이 안오는데
여러가지 조건이 있을때 특정 값을 업데이트를 할때 사용?
웹에서 이점이 있을것 같은데.. 일단 이렇게 쓰이는구나... 정도만 이해

switch와 동일하게 case에서 default를 사용하게 되면
블로킹 없이(비동기적인) 송/수신을 하고자 할 때 default를 사용하면 된다.

select {
case i := <-c:
    // i를 사용
default:
    // c로부터의 수신은 블록된 상태
}

뒤에 연습문제도 있지만 집중력이 떨어짐
이후에 더 살펴보기 위해서는 레퍼런스, 튜토리얼, 비디오 자료갸 있는 Go 문서를 참고

+ Recent posts