GO 동시성 프로그래밍- 3
3장 GO의 동시성 구성요소
고루틴
- 당연히 main도 고루틴이다.
- 고루틴은 다른 코드와 함께 동시에 실행되는 함수이다.
- 하지만 꼭 병렬로 실행되는 것은 아니다.
- 고루틴은 가볍다.
- 익명 함수도 지원
sayHello := func() {
fmt.Println("hello")
}()
go sayHello
- 고루틴은 OS쓰레드가 아니며
코루틴
이라는 더 높은 추상화이다.
코루틴은 단순히 동시에 실행되는 서브루틴으로서, 비선점적, 다시말해 인터럽트 할 수 없다. 대신 코루틴은 잠시 중단하거나 재 진입 할 수 있도록 여러개의 지점을 가지고 있다.
- M:N의 스케줄러로써 M개의 그린 스레드를 N개의 OS스레드에 매핑한다는 의미이다.
-
fork-join
이라는 동시성 모델을 따르는데,fork
는 부모와 자식이 동시에 실행되며,join
은 동시에 실행된게 합쳐진다.sync.waitgroup
var wg sync.waitGroup
wg.add(1)
go func() {
defer wg.done()
fmt.Println("hello")
}()
wg.wait()
생각해봐야될 문제
// 아래 문제는 순차로 출력될것을 기대하지만 아니다.
// good day만 3번출력
// 이유는 go는 언제 실행될지 모르기 때문이다. 순차적으로 실행될것을 기대했으나 실제로는 good day까지 인덱스가 흘러간 후 go routine이 동작함
var wg sync.WaitGroup
for _, s := range []strings{"hello", "greetings", "good day"} {
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println(s)
}()
}
wg.wait()
// 해결 하기 위해서는 아래와 같이 넘겨주면 된다
var wg sync.WaitGroup
for _, s := range []strings{"hello", "greetings", "good day"} {
wg.Add(1)
go func(s string) {
defer wg.Done()
fmt.Println(str)
}(s)
}
wg.wait()
Sync 패키징
- WaitGroup
- Mutex, RWMutex
- Cond
- Once
- pool
- sync.Pool을 인스턴스화 할 때 호출 시 스레드로부터 안전한 New 멤버 변수를 전달한다.
- Get에서 인스턴스를 받았을 때 , 돌려받은 객체의 상태에 대한 가정을 해서는 안된다.
- Pool에서 꺼낸 객채로 작업을 마치면 반드시 Put을 호출한다. 그렇게 하지 않으면 Pool은 아무런 소용이 없다. 보통 이 작업은 defer로 이루어진다.
- 풀 내에 있는 객체들은 구조가 거의 균일하여야 한다.
채널
<-chan struct{} // 읽기만
chan<- struct{} // 쓰기만
- go는 필요할 때 양방향 채널을 묵시적으로 단 방향 채널로 변환한다.
- 가득찬 채널에 쓰려고 하는 고루틴은 채널이 비워질때까지 기다리며, 비어있는 채널에서 읽으려는 고루틴은 적어도 하나의 항목이 있을때까지 기다린다.
- 읽기 연산은 두번쨰 리턴 값이 가능한대 해당 값은 닫힌 여부이다.
select{
case c, ok <- streamCh:
if !ok {
fmt.Println("닫혔다")
}
switch c.type {
...
}
}
- 버퍼링되지 않은 채널은 단순히 여유용량이 0인 버퍼링된 채널과 같다.
- 버퍼가 가득찬 채널에 쓰기 연산을 하면, 버퍼가 비워질 때까지 대기한다. 송신자가 없는 버퍼가 빈 채널에 읽기 연산을 하면, 송신이 발생할 때까지 대기한다.
채널 생성 시 참고사항
채널을 소유한 고루틴은 반드시 다음을 수행해야 한다.
- 채널을 인스턴스화 한다.
- 쓰기를 수행하거나 다른 고루틴으로 소유권을 넘긴다.
- 채널을 닫는다.
- 이 목록에 있는 앞의 세가지를 캡슐화하고, 이를 읽기 채널을 통해 노출한다.
이렇게 책임을 소유자에게 부여하면, 다음과 같은 효과가 있다.
- 우리가 채널을 초기화하기 때문에 nil 채널에 쓰는 것으로 인한 데드락의 위험을 제거할 수 있다.
- 우리가 채널을 초기화하기 때문에 nil 채널을 닫을 위험이 없다.
- 우리가 채널이 닫히는 시기를 결정하기 때문에, 닫힌 채널에 쓰는 것으로 인한 패닉의 위험을 없앨 수 있다.
- 우리가 채널이 닫히는 시점을 결정하기 때문에 채널을 두번 이상 닫는 것으로 인한 패닉의 위험을 제거할 수 있다.
- 우리 채널에 부적절한 쓰기가 일어나는 것을 방지하기 위해 컴파일 시점에 타입 검사기를 사용한다.
채널의 소비자는 이제 두가지 사항만 신경쓰면 된다.
- 언제 채널이 닫히는지 아는 것
- 어떤 이유로든 대기가 발생하면 책임있게 처리하는 것
Select
-
switch 블록과는 다르게 select 블록의 case 문은 순차적으로 테스트되지 않는다.
-
조건이 하나도 충족되지 않는다고 다음 조건으로 넘어가지도 않는다.
- 대신 모든 채널 읽기와 쓰기를 동시에 고려한다
- 준비된 채널이 없는 경우 select 문 전체가 중단되 대기한다. 그런 다음 채널들 중 하나가 준비되면 해당 연산이 진행되고 관련 구문들이 실행된다.
댓글남기기