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

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

Напишем пример на Go с легкими, параллельно выполняемыми потоками-горутинами  —  просто добавляем ключевое слово go перед функцией:

go f(x, y, z) {}

В WaitGroup ожидается завершение горутин, вызовом Add в основной горутине задается количество ожидаемых горутин, затем по выполнении каждой из них вызывается Done, одновременно в Wait выполняется блокировка до завершения всех горутин:

var wg sync.WaitGroup

wg.Add(int)
wg.Done()
wg.Wait()

Сначала напишем программу без мьютекса:

package main

import (
"fmt"
"sync"
"time"
)

func main() {
counter := 0
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < 10; j++ {
time.Sleep(time.Nanosecond)
counter++
}
}()
}
wg.Wait()
fmt.Println("Counter:", counter)
}

Функцией main инициализируются счетчик и WaitGroup, затем запускается 10 горутин. Каждой горутиной счетчик увеличивается  —  10 раз с краткими интервалами.

Теоретически программой выводится значение счетчика 100, но иногда  —  попробуйте сами  —  это не получается. Дело в том, что некоторые увеличения перекрываются: одно выполняется, другие  —  нет. Это как гонка между ними. Если увеличить диапазон цикла или количество горутин, проблема усугубляется экспоненциально.

Тут-то и пригодится мьютекс, на Go реализовать его легко. Объявляем переменную Mutex, методом Lock захватываем мьютекс, блокируя доступ других горутин к заблокированной части кода, и методом Unlock освобождаем мьютекс для других горутин:

var mu sync.Mutex

mu.Lock()
mu.Unlock()

Перепишем программу и посмотрим, что получится:

package main

import (
"fmt"
"sync"
"time"
)

func main() {
counter := 0
var wg sync.WaitGroup
var mu sync.Mutex

for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < 10; j++ {
time.Sleep(time.Nanosecond)
mu.Lock()
counter++
mu.Unlock()
}
}()
}

wg.Wait()
fmt.Println("Counter:", counter)
}

Видите разницу?

Чтобы писать на Go эффективные и безопасные программы с параллельным выполнением, нужны соответствующие инструменты: горутины, WaitGroups и мьютексы. Применяйте их для управления несколькими задачами одновременно, предотвращая состояния гонки и обеспечивая корректную синхронизацию. Благодаря этим концепциям создаются надежные, производительные приложения.

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи Kerala Blockchain Academy: What is mutex? How does it prevent race conditions?

Предыдущая статьяС этим инструментом код чище в 10 000 раз
Следующая статья70% интервьюеров задают эти 5 вопросов по React.js