- 책 또는 웹사이트의 내용을 복제하여 다른 곳에 게시하는 것을 금지합니다.
- 책 또는 웹사이트의 내용을 발췌, 요약하여 강의 자료, 발표 자료, 블로그 포스팅 등으로 만드는 것을 금지합니다.
동기화 객체 사용하기
이재홍 http://www.pyrasis.com 2014.12.17 ~ 2015.02.07
읽기, 쓰기 뮤텍스 사용하기
읽기, 쓰기 뮤텍스는 읽기 동작과 쓰기 동작을 나누어 잠금(락)을 걸 수 있습니다.
- 읽기 락(Read Lock): 읽기 락끼리는 서로를 막지 않습니다. 하지만 읽기 시도 중에 값이 바뀌면 안 되므로 쓰기 락은 막습니다.
- 쓰기 락(Write Lock): 쓰기 시도 중에 다른 곳에서 이전 값을 읽으면 안 되고, 다른 곳에서 값을 바꾸면 안 되므로 읽기, 쓰기 락 모두 막습니다.
sync 패키지에서 제공하는 읽기, 쓰기 뮤텍스 구조체와 함수는 다음과 같습니다.
- sync.RWMutex
- func (rw *RWMutex) Lock(), func (rw *RWMutex) Unlock(): 쓰기 뮤텍스 잠금, 잠금 해제
- func (rw *RWMutex) RLock(), func (rw *RWMutex) RUnlock(): 읽기 뮤텍스 잠금 및 잠금 해제
먼저 읽기 쓰기 뮤텍스를 사용하지 않고 고루틴에서 값을 출력해보겠습니다.
package main
import (
"fmt"
"runtime"
"time"
)
func main() {
runtime.GOMAXPROCS(runtime.NumCPU()) // 모든 CPU 사용
var data int = 0
go func() { // 값을 쓰는 고루틴
for i := 0; i < 3; i++ {
data += 1 // data에 값 쓰기
fmt.Println("write : ", data) // data 값을 출력
time.Sleep(10 * time.Millisecond) // 10 밀리초 대기
}
}()
go func() { // 값을 읽는 고루틴
for i := 0; i < 3; i++ {
fmt.Println("read 1 : ", data) // data 값을 출력(읽기)
time.Sleep(1 * time.Second) // 1초 대기
}
}()
go func() { // 값을 읽는 고루틴
for i := 0; i < 3; i++ {
fmt.Println("read 2 : ", data) // data 값을 출력(읽기)
time.Sleep(2 * time.Second) // 2초 대기
}
}()
time.Sleep(10 * time.Second) // 10초 동안 프로그램 실행
}
값을 쓰는 고루틴을 1개 생성하고, 값을 읽는 고루틴을 2개 생성합니다. 값을 쓰는 고루틴은 10 밀리초마다 반복하고, 값을 읽는 고루틴은 1초, 2초마다 반복합니다. 실행해보면 매번 불규칙적인 모양으로 출력될 것이고, 특별한 순서를 찾기 어렵습니다.
예제는 변수에 1만 더하는 간단한 상황이라 큰 문제가 없지만, 실제 상황에서는 중요한 데이터를 쓰고 있는데 다른 곳에서 이전 데이터를 읽는다던지, 읽기 시도 중에 값이 바뀐다던지 하는 상황이 생길 수 있습니다.
read 1 : 1
read 2 : 1
write : 1
write : 2
write : 3
read 1 : 3
read 1 : 3
read 2 : 3
read 2 : 3
이제 읽기, 쓰기 동작 실행이 완벽하게 보장되도록 읽기, 쓰기 뮤텍스를 사용해보겠습니다.
package main
import (
"fmt"
"runtime"
"sync"
"time"
)
func main() {
runtime.GOMAXPROCS(runtime.NumCPU()) // 모든 CPU 사용
var data int = 0
var rwMutex = new(sync.RWMutex) // 읽기, 쓰기 뮤텍스 생성
go func() { // 값을 쓰는 고루틴
for i := 0; i < 3; i++ {
rwMutex.Lock() // 쓰기 뮤텍스 잠금, 쓰기 보호 시작
data += 1 // data에 값 쓰기
fmt.Println("write : ", data) // data 값을 출력
time.Sleep(10 * time.Millisecond) // 10 밀리초 대기
rwMutex.Unlock() // 쓰기 뮤텍스 잠금 해제, 쓰기 보호 종료
}
}()
go func() { // 값을 읽는 고루틴
for i := 0; i < 3; i++ {
rwMutex.RLock() // 읽기 뮤텍스 잠금, 읽기 보호 시작
fmt.Println("read 1 : ", data) // data 값을 출력(읽기)
time.Sleep(1 * time.Second) // 1초 대기
rwMutex.RUnlock() // 읽기 뮤텍스 잠금 해제, 읽기 보호 종료
}
}()
go func() { // 값을 읽는 고루틴
for i := 0; i < 3; i++ {
rwMutex.RLock() // 읽기 뮤텍스 잠금, 읽기 보호 시작
fmt.Println("read 2 : ", data) // data 값을 출력(읽기)
time.Sleep(2 * time.Second) // 2초 대기
rwMutex.RUnlock() // 읽기 뮤텍스 잠금 해제, 읽기 보호 종료
}
}()
time.Sleep(10 * time.Second) // 10초 동안 프로그램 실행
}
읽기, 쓰기 뮤텍스는 sync.RWMutex를 할당한 뒤에 고루틴에서 RLock, RUnlock, Lock, Unlock 함수로 사용합니다. 읽기 동작을 시작할 부분에서 RLock 함수를 사용하고, 읽기 동작을 끝낼 부분에서 RUnlock 함수를 사용합니다. 그리고 쓰기 동작을 시작할 부분에서 Lock 함수를 사용하고, 쓰기 동작을 끝낼 부분에서 Unlock 함수를 사용합니다.
RLock, RUnlock, Lock, Unlock 함수는 반드시 짝을 맞춰야 하며 짝이 맞지 않으면 데드락(deadlock, 교착 상태)이 발생하므로 주의합니다.
실행해보면 각각의 순서는 매번 달라지지만 실행되는 모양은 규칙적입니다. 즉 read 1, read 2 읽기 동작이 모두 끝나야 write 쓰기 동작이 시작됩니다. 마찬가지로 쓰기 동작이 끝나야 읽기 동작이 시작됩니다. 읽기 동작끼리는 서로를 막지 않으므로 항상 동시에 실행됩니다.
read 1 : 0
read 2 : 0 ← read 1, read 2 읽기 동작이 모두 끝나야
write : 1 ← write 쓰기 동작이 시작됨
read 1 : 1
read 2 : 1
write : 2
read 2 : 2
read 1 : 2
write : 3
읽기, 쓰기 뮤텍스는 중요한 쓰기 작업을 할 때 다른 곳에서 이전 데이터를 읽지 못하도록 방지하거나, 읽기 작업을 할 때 데이터가 바뀌는 상황을 방지할 때 사용합니다. 특히 읽기, 쓰기 뮤텍스는 쓰기 동작보다 읽기 동작이 많을 때 유리합니다.
저작권 안내
이 웹사이트에 게시된 모든 글의 무단 복제 및 도용을 금지합니다.- 블로그, 게시판 등에 퍼가는 것을 금지합니다.
- 비공개 포스트에 퍼가는 것을 금지합니다.
- 글 내용, 그림을 발췌 및 요약하는 것을 금지합니다.
- 링크 및 SNS 공유는 허용합니다.