Mutexes

Мьютексы (Mutexes)|Mutexes #

// В предыдущем примере мы увидели, как управлять простым
// состоянием счетчика с помощью [атомарных операций](atomic-counters).
// Для более сложного состояния мы можем использовать <em>мьютекс(http://en.wikipedia.org/wiki/Mutual_exclusion)</em>
// для безопасного доступа к данным в нескольких горутинах.

package main

import (
	"fmt"
	"math/rand"
	"sync"
	"sync/atomic"
	"time"
)

func main() {

	// Для нашего примера `state` будет картой.
	var state = make(map[int]int)

	// Этот `mutex` будет синхронизировать доступ к `state`.
	var mutex = &sync.Mutex{}

	// Мы будем отслеживать, сколько операций чтения и
	// записи мы выполняем.
	var readOps uint64
	var writeOps uint64

	// Здесь мы запускаем 100 горутин для выполнения
	// повторных операций чтения по состоянию, один раз
	// в миллисекунду в каждой горутине.
	for r := 0; r < 100; r++ {
		go func() {
			total := 0
			for {

				// Для каждого чтения мы выбираем ключ для
				// доступа, блокируем `mutex` с помощью `Lock()` ,
				// чтобы обеспечить исключительный доступ к
				// `состоянию`, читаем значение в выбранном ключе,
				// разблокируем мьютекс `Unlock()` и увеличиваем
				// количество `readOps`.
				key := rand.Intn(5)
				mutex.Lock()
				total += state[key]
				mutex.Unlock()
				atomic.AddUint64(&readOps, 1)

				// Немного ждем между чтениями.
				time.Sleep(time.Millisecond)
			}
		}()
	}

	// Запустим так же 10 горутин для симуляции записи,
	// так же как мы делали для чтения.
	for w := 0; w < 10; w++ {
		go func() {
			for {
				key := rand.Intn(5)
				val := rand.Intn(100)
				mutex.Lock()
				state[key] = val
				mutex.Unlock()
				atomic.AddUint64(&writeOps, 1)
				time.Sleep(time.Millisecond)
			}
		}()
	}

	// Пусть 10 горутин работают над `состоянием` и
	// `мьютексом` на секунду.
	time.Sleep(time.Second)

	// Смотрим финальное количество операций
	readOpsFinal := atomic.LoadUint64(&readOps)
	fmt.Println("readOps:", readOpsFinal)
	writeOpsFinal := atomic.LoadUint64(&writeOps)
	fmt.Println("writeOps:", writeOpsFinal)

	// С окончательной блокировкой `состояния` смотрим,
	// как все закончилось.
	mutex.Lock()
	fmt.Println("state:", state)
	mutex.Unlock()
}
# Запуск программы показывает, что мы выполнили
# около 90000 операций в нашем синхронизированном
# с `мьютексом`состоянии.
$ go run mutexes.go
readOps: 83285
writeOps: 8320
state: map[1:97 4:53 0:33 2:15 3:2]

# Далее мы рассмотрим реализацию той же задачи управления
# состоянием с использованием только горутин и каналов.