124 lines
4.8 KiB
Go
124 lines
4.8 KiB
Go
// В предыдущем примере мы использовали явную блокировку
|
||
// с [мьютексами](mutexes) для синхронизации доступа к
|
||
// общему состоянию между несколькими горутинами. Другой
|
||
// вариант - использовать встроенные функции синхронизации
|
||
// для горутин и каналов для достижения того же результата.
|
||
// Этот подход, основанный на каналах, согласуется с идеями
|
||
// Go о совместном использовании памяти путем обмена
|
||
// данными и владения каждой частью данных ровно 1 горутиной.
|
||
|
||
package main
|
||
|
||
import (
|
||
"fmt"
|
||
"math/rand"
|
||
"sync/atomic"
|
||
"time"
|
||
)
|
||
|
||
// В этом примере наше состояние будет принадлежать
|
||
// единственной горутине. Это гарантирует, что данные
|
||
// никогда не будут повреждены при одновременном доступе.
|
||
// Чтобы прочитать или записать это состояние, другие
|
||
// горутины будут отправлять сообщения горутин-владельцу
|
||
// и получать соответствующие ответы. Эти `структуры`
|
||
// `readOp` и `writeOp` инкапсулируют эти запросы и
|
||
// способ, которым владеет горутина-ответчик.
|
||
// In this example our state will be owned by a single
|
||
// goroutine. This will guarantee that the data is never
|
||
// corrupted with concurrent access. In order to read or
|
||
// write that state, other goroutines will send messages
|
||
// to the owning goroutine and receive corresponding
|
||
// replies. These `readOp` and `writeOp` `struct`s
|
||
// encapsulate those requests and a way for the owning
|
||
// goroutine to respond.
|
||
type readOp struct {
|
||
key int
|
||
resp chan int
|
||
}
|
||
type writeOp struct {
|
||
key int
|
||
val int
|
||
resp chan bool
|
||
}
|
||
|
||
func main() {
|
||
|
||
// Как и прежде, мы посчитаем, сколько операций мы
|
||
// выполняем.
|
||
var readOps uint64
|
||
var writeOps uint64
|
||
|
||
// Каналы `чтения` и `записи` будут использоваться
|
||
// другими горутинами для выдачи запросов на чтение
|
||
// и запись соответственно.
|
||
reads := make(chan readOp)
|
||
writes := make(chan writeOp)
|
||
|
||
// Эта горутина, которой принадлежит состояние, она же
|
||
// является картой, как в предыдущем примере, но теперь
|
||
// является частной для горутины с сохранением состояния.
|
||
// Она постоянно выбирает каналы `чтения` и `записи`,
|
||
// отвечая на запросы по мере их поступления. Ответ
|
||
// выполняется, сначала выполняя запрошенную операцию,
|
||
// а затем отправляя значение по каналу `resp`,
|
||
// соответственно, чтобы указать успешность (и
|
||
// ребуемое значение в случае `reads`).
|
||
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
|
||
}
|
||
}
|
||
}()
|
||
|
||
// Запускаем 100 горутин для выдачи операций чтения
|
||
// в горутину владеющую состоянием, через канал `reads`.
|
||
// Каждое чтение требует создания `readOp`, отправки
|
||
// его по каналу `reads` и получения результата по
|
||
// `resp` каналу.
|
||
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)
|
||
}
|