가장 빨리 만나는 Go 언어 Unit 35. 동기화 객체 사용하기
저작권 안내
- 책 또는 웹사이트의 내용을 복제하여 다른 곳에 게시하는 것을 금지합니다.
- 책 또는 웹사이트의 내용을 발췌, 요약하여 발표 자료, 블로그 포스팅 등으로 만드는 것을 금지합니다.
동기화 객체 사용하기
이재홍 http://www.pyrasis.com 2014.12.17 ~ 2015.02.07
Go 언어에서는 채널 이외에도 고루틴의 실행 흐름을 제어하는 동기화 객체를 제공합니다.
대표적인 동기화(synchronization) 객체는 다음과 같습니다.
- Mutex: 뮤텍스입니다. 상호 배제(mutual exclusion)라고도 하며 여러 스레드(고루틴)에서 공유되는 데이터를 보호할 때 주로 사용합니다.
- RWMutex: 읽기/쓰기 뮤텍스입니다. 읽기와 쓰기 동작을 나누어서 잠금(락)을 걸 수 있습니다.
- Cond: 조건 변수(condition variable)입니다. 대기하고 있는 하나의 객체를 깨울 수도 있고 여러 개를 동시에 깨울 수도 있습니다.
- Once: 특정 함수를 딱 한 번만 실행할 때 사용합니다.
- Pool: 멀티 스레드(고루틴)에서 사용할 수 있는 객체 풀입니다. 자주 사용하는 객체를 풀에 보관했다가 다시 사용합니다.
- WaitGroup: 고루틴이 모두 끝날 때까지 기다리는 기능입니다.
- Atomic: 원자적 연산이라고도 하며 더 이상 쪼갤 수 없는 연산이라는 뜻입니다. 멀티 스레드(고루틴), 멀티코어 환경에서 안전하게 값을 연산하는 기능입니다.
뮤텍스 사용하기
뮤텍스는 여러 고루틴이 공유하는 데이터를 보호할 때 사용하며 sync 패키지에서 제공하는 뮤텍스 구조체와 함수는 다음과 같습니다.
- sync.Mutex
- func (m *Mutex) Lock(): 뮤텍스 잠금
- func (m *Mutex) Unlock(): 뮤텍스 잠금 해제
다음은 고루틴 두 개에서 각각 1,000번씩 슬라이스에 값을 추가합니다.
package main
import (
"fmt"
"runtime"
"time"
)
func main() {
runtime.GOMAXPROCS(runtime.NumCPU()) // 모든 CPU 사용
var data = []int{} // int형 슬라이스 생성
go func() { // 고루틴에서
for i := 0; i < 1000; i++ { // 1000번 반복하면서
data = append(data, 1) // data 슬라이스에 1을 추가
runtime.Gosched() // 다른 고루틴이 CPU를 사용할 수 있도록 양보
}
}()
go func() { // 고루틴에서
for i := 0; i < 1000; i++ { // 1000번 반복하면서
data = append(data, 1) // data 슬라이스에 1을 추가
runtime.Gosched() // 다른 고루틴이 CPU를 사용할 수 있도록 양보
}
}()
time.Sleep(2 * time.Second) // 2초 대기
fmt.Println(len(data)) // data 슬라이스의 길이 출력
}
출력 결과
1883 (매번 달라질 수 있음)
실행을 해보면 대략 1800~1990 사이의 값이 나옵니다. data 슬라이스에 1을 2,000번 추가했으므로 data의 길이가 2000이 되어야 하는데 그렇지가 않습니다. 두 고루틴이 경합을 벌이면서 동시에 data에 접근했기 때문에 append 함수가 정확하게 처리되지 않은 상황입니다. 이러한 상황을 경쟁 조건(Race condition)이라고 합니다.
runtime.Gosched 함수는 다른 고루틴이 CPU를 사용할 수 있도록 양보(yield)합니다. 지금까지 time.Sleep 함수를 사용했지만 runtime.Gosched 함수가 좀 더 명확합니다.