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

Напишем пример на 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 и мьютексы. Применяйте их для управления несколькими задачами одновременно, предотвращая состояния гонки и обеспечивая корректную синхронизацию. Благодаря этим концепциям создаются надежные, производительные приложения.
Читайте также:
- 7 типичных ошибок в Go-интерфейсах
- Чистая реализация структуры проекта на Go
- Как использовать перечисления в Golang
Читайте нас в Telegram, VK и Дзен
Перевод статьи Kerala Blockchain Academy: What is mutex? How does it prevent race conditions?