gobyexample/examples/stateful-goroutines/stateful-goroutines.go

124 lines
4.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// В предыдущем примере мы использовали явную блокировку
// с [мьютексами](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)
}