이전 예제에서 다중 고루틴의 공유 자원 접근을 동기화 하기위해 mutexes로 명시적인 잠금(locking)을 사용했습니다. 다른 방법인 고루틴과 채널의 내장 동기화 기능을 사용하여 같은 일을 할 수 있습니다. 채널 기반 접근법은 Go의 통신을 통한 메모리 공유와 정확히 한 고루틴이 각 데이터의 일부를 소유한다는 아이디어에 기반합니다. |
|
package main
|
|
import (
"fmt"
"math/rand"
"sync/atomic"
"time"
)
|
|
이 예제에서 상태는 한 고루틴에 의해 소유됩니다. 이는 데이터가 동시 접근으로인해 손상되지 않음을 보장합니다. 상태를 읽거나 쓰기위해, 다른 고루틴들은 소유중인 고루틴으로 메시지를 보내고 응답을 받습니다. 다음 |
type readOp struct {
key int
resp chan int
}
type writeOp struct {
key int
val int
resp chan bool
}
|
func main() {
|
|
이전과 같이 얼마나 연산이 얼마나 이루어지는지를 카운팅합니다. |
var readOps uint64 = 0
var writeOps uint64 = 0
|
|
reads := make(chan *readOp)
writes := make(chan *writeOp)
|
다음 고루틴은 |
go func() {
var state = make(map[int]int)
for {
select {
case read := <-reads:
read.resp <- state[read.key]
case write := <-writes:
state[write.key] = write.val
write.resp <- true
}
}
}()
|
다음은 |
for r := 0; r < 100; r++ {
go func() {
for {
read := &readOp{
key: rand.Intn(5),
resp: make(chan int)}
reads <- read
<-read.resp
atomic.AddUint64(&readOps, 1)
time.Sleep(time.Millisecond)
}
}()
}
|
마찬가지로 10개의 쓰기 고루틴을 띄웁니다. |
for w := 0; w < 10; w++ {
go func() {
for {
write := &writeOp{
key: rand.Intn(5),
val: rand.Intn(100),
resp: make(chan bool)}
writes <- write
<-write.resp
atomic.AddUint64(&writeOps, 1)
time.Sleep(time.Millisecond)
}
}()
}
|
1초간 고루틴들의 작업을 대기합니다. |
time.Sleep(time.Second)
|
마지막으로 각 연산 횟수를 캡쳐하고 리포팅합니다. |
readOpsFinal := atomic.LoadUint64(&readOps)
fmt.Println("readOps:", readOpsFinal)
writeOpsFinal := atomic.LoadUint64(&writeOps)
fmt.Println("writeOps:", writeOpsFinal)
}
|
프로그램을 실행하면 고루틴 기반 상태 관리 예시는 약 총 80,000번의 연산을 수행함을 보여줍니다. |
$ go run stateful-goroutines.go
readOps: 71708
writeOps: 7177
|
이 특별한 케이스인 고루틴 기반 접근법은 뮤텍스 기반보다 조금 더 복잡합니다. 이는 특정한 경우에 유용할 수 있는데, 예를 들면 다른 채널들이 관련되어 있거나 에러가 발생하기 쉬운 다중 뮤텍스들을 관리하는 경우가 있습니다. 여러분의 프로그램의 정확성을 이해하는 것과 관련해 가장 자연스럽게 느껴지는 방식을 사용해야합니다. |
다음 예제: 정렬.