Доступ к данным в параллельном режиме с использованием мьютексов

GolangGolangBeginner
Практиковаться сейчас

This tutorial is from open-source community. Access the source code

💡 Этот учебник переведен с английского с помощью ИИ. Чтобы просмотреть оригинал, вы можете перейти на английский оригинал

Введение

Этот лабораторный практикум旨在演示如何使用мьютексы для безопасного доступа к данным из нескольких goroutine.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL go(("Golang")) -.-> go/ConcurrencyGroup(["Concurrency"]) go/ConcurrencyGroup -.-> go/mutexes("Mutexes") subgraph Lab Skills go/mutexes -.-> lab-15490{{"Доступ к данным в параллельном режиме с использованием мьютексов"}} end

Mutexes

Проблема, которую необходимо решить в этом лабораторном практикуме, заключается в том, чтобы увеличивать именованный счетчик в цикле с использованием нескольких goroutine и обеспечить синхронизацию доступа к счетчику.

  • Используйте структуру Container, чтобы хранить карту счетчиков.
  • Используйте Mutex, чтобы синхронизировать доступ к карте counters.
  • Структура Container должна иметь метод inc, который принимает строку name и увеличивает соответствующий счетчик в карте counters.
  • Метод inc должен заблокировать мьютекс перед доступом к карте counters и разблокировать его в конце функции с использованием инструкции defer.
  • Используйте структуру sync.WaitGroup, чтобы дождаться завершения goroutine.
  • Используйте функцию fmt.Println, чтобы вывести карту counters.
## Запуск программы показывает, что счетчики
## обновляются как ожидается.

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

Ниже представлен полный код:

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

package main

import (
	"fmt"
	"sync"
)

// Container хранит карту счетчиков; так как мы хотим
// обновлять ее одновременно из нескольких goroutine, мы
// добавляем `Mutex`, чтобы синхронизировать доступ.
// Обратите внимание, что мьютексы не должны копироваться, поэтому если эта
// `структура` передается по ссылке, это должно быть сделано
// указателем.
type Container struct {
	mu       sync.Mutex
	counters map[string]int
}

func (c *Container) inc(name string) {
	// Заблокируйте мьютекс перед доступом к `counters`; разблокируйте
	// его в конце функции с использованием инструкции [defer](defer).
	c.mu.Lock()
	defer c.mu.Unlock()
	c.counters[name]++
}

func main() {
	c := Container{
		// Обратите внимание, что нулевое значение мьютекса можно использовать как есть, поэтому здесь не требуется инициализация.
		counters: map[string]int{"a": 0, "b": 0},
	}

	var wg sync.WaitGroup

	// Эта функция увеличивает именованный счетчик
	// в цикле.
	doIncrement := func(name string, n int) {
		for i := 0; i < n; i++ {
			c.inc(name)
		}
		wg.Done()
	}

	// Запустите несколько goroutine одновременно; обратите
	// внимание, что они все обращаются к одному и тому же `Container`,
	// и два из них обращаются к одному и тому же счетчику.
	wg.Add(3)
	go doIncrement("a", 10000)
	go doIncrement("a", 10000)
	go doIncrement("b", 10000)

	// Подождите, пока goroutine завершатся
	wg.Wait()
	fmt.Println(c.counters)
}

Резюме

В этом лабораторном практикуме мы узнали, как использовать мьютексы для безопасного доступа к данным из нескольких goroutine. Мы создали структуру Container, чтобы хранить карту счетчиков, и использовали Mutex, чтобы синхронизировать доступ к карте counters. Мы также реализовали метод inc, чтобы увеличить именованный счетчик, и использовали структуру sync.WaitGroup, чтобы дождаться завершения goroutine. Наконец, мы вывели карту counters с использованием функции fmt.Println.