Найти тему
Golang-news

Использование мьютекса в Go (Golang) — с примерами

Оглавление

В этом посте мы обсудим, почему мы используем мьютексы в Go, и узнаем, как использовать мьютекс для блокировки данных и исправления условий гонки.

Go позволяет нам одновременно запускать код с помощью горутин. Однако, когда параллельные процессы обращаются к одному и тому же фрагменту данных, это может привести к race conditions.

Мьютексы — это структуры данных, предоставляемые пакетом sync . Они могут помочь нам заблокировать разные разделы данных , чтобы только одна горутина могла получить к ним доступ в каждый момент времени.

Использование мьютекса для блокировки данных

Давайте рассмотрим пример, когда параллельные горутины могут повредить часть данных, обратившись к ней одновременно:

-2

Запуск этого кода даст нам следующий неожиданный результат:

1 is even

Это происходит потому, что горутина 1 на самом деле обращается nдважды: один раз, чтобы проверить, четно ли оно, и еще раз, чтобы вывести его значение. В промежутках между этими шагами горутина 2 увеличивает значение n.

Выводимое значение теперь отличается от проверенного значения. Это известно как race conditions.

-3

Мы должны гарантировать, что это n не должно быть написано на случай, если его читает другая горутина, и наоборот.

Мы можем использовать этот тип, чтобы предотвратить одновременный sync.Mutex доступ к нескольким горутинам : n

-4

Вызов m.Lock «заблокирует» мьютекс. Если какая-либо другая горутина вызовет m.Lock, она заблокирует поток до тех пор, пока m.Unlock не будет вызвана.

Если горутина вызывается m.Lock перед первым доступом для чтения/записи к соответствующим данным и вызывается m.Unlock после последнего, гарантируется, что между этим периодом горутина будет иметь эксклюзивный доступ к данным.

Если вы хотите удерживать блокировку для всей области действия функции, лучше отложить вызов , Unlock а не вызывать его самостоятельно в конце.
-5

Вот почему, когда мы запускаем приведенный выше код , мы получаем правильный вывод:

0 is even

Мьютекс чтения/записи — тип sync.RWMutex

Иногда для данных допустимо параллельное чтение, пока записи остаются атомарными.

В этом случае мы можем sync.RWMutex тип, который имеет разные блокировки для чтения и записи данных:

-6

При запуске этого кода мы можем наблюдать, что горутины 1 и 2 могут обращаться nодновременно, но операции чтения (1 и 2) и записи (3) заблокированы друг от друга , и одна может начаться только после завершения другой.

-7

Это может сделать ваше приложение более производительным, если оно считывает эти данные несколько раз, поскольку чтение может выполняться одновременно.

Добавление мьютексов в структуры

Если вы хотите связать мьютекс вместе с его данными, вы можете рассмотреть возможность использования композиции структуры.

Если бы мы хотели сделать это для предыдущего примера, мы могли бы создать структуру для хранения числового значения, а также включить мьютекс:

-8

Затем мы можем использовать мьютекс внутри самой структуры для обработки блокировки и разблокировки:

-9

Запуск этого кода даст тот же результат, что и раньше.

Это полезно по нескольким причинам:

  1. Если у вас есть несколько экземпляров данных, которым требуется эксклюзивный доступ, объединение мьютекса вместе с самими данными сделает их менее запутанными и более читабельными.
  2. Данные могут быть переданы в качестве аргументов функции, а мьютекс будет передан по умолчанию.

Если вам не нужен общий мьютекс для нескольких фрагментов данных, рекомендуется включить мьютекс в структуру.

Общие ловушки

Хотя мьютексы могут показаться отличным решением, легко попасть в некоторые распространенные ловушки.

Всякий раз, когда вы вызываете Lock метод, вы должны убедиться, что Unlock он в конечном итоге вызывается. Это может показаться простым, но легко пропустить. Рассмотрим этот пример:

-10

Здесь мы вызываем Unlock метод в конце функции, а не используем defer. Теперь, если nчетно, мы напечатаем соответствующий оператор и вернемся из горутины, но никогда не будем вызываться.

Если это произойдет, любая горутина, пытающаяся получить доступ к той же блокировке, будет заблокирована навсегда. Использование defer для вызова Unlock метода может помочь вам избежать этого.

Всегда снимайте блокировку, как только закончите доступ к данным, и никогда позже — вы просто потратите ресурсы впустую.

Например, если вам нужно было выполнить какую-то работу после доступа n, использование defer может быть неуместным:

-11

В этом случае n.Unlock будет вызываться после последнего time.Sleep вызова, хотя мы можем снять блокировку, как только закончим печать результата. Используя defer, мы потенциально задержали другие горутины, которым требуется доступ к этой блокировке.

В таких случаях лучше снять блокировку вручную после того, как вы закончите доступ к данным:

-12

Таким образом, мьютекс — отличный инструмент для предотвращения неупорядоченного доступа к данным . Существует несколько способов использования мьютекса и множество подводных камней, которые могут возникнуть, поэтому обязательно оцените свой вариант использования, прежде чем выбрать правильный подход.

Чтобы узнать больше о мьютексах в Go, вы можете посмотреть страницу документации стандартной библиотеки «sync» .