Мьютексы

Последнее обновление: 12.01.2018

Для упрощения синхронизации между горутинами в Go имеется пакет sync, который предоставляет ряд возможностей, в частности мьютексы. Мьютексы позволяют разграничить доступ к некоторым общим ресурсам, гарантируя, что только одна горутина имеет к ним доступ в определенный момент времени. И пока одна горутина не освободит общий ресурс, другая горутина не может с ним работать.

На уровне кода мьютекс представляет тип sync.Mutex. Для блокирования доступа к общему разделяемому ресурсу у мьютекса вызывается метод Lock(), а для разблокировки доступа - метод Unlock().

В какой ситуации нам могут помочь мьютексы? Рассмотрим следующую ситуацию:

package main
import "fmt"

var counter int = 0				//	общий ресурс
func main() {

	ch := make(chan bool)		 // канал
	for i := 1; i < 5; i++{
		go work(i, ch)
	}
	// ожидаем завершения всех горутин
	for i := 1; i < 5; i++{
		<-ch
	}
	fmt.Println("The End")
}
func work (number int, ch chan bool){
	counter = 0
	for k := 1; k <= 5; k++{
		counter++
		fmt.Println("Goroutine", number, "-", counter)
	}
	ch <- true
}

Функция work сбрасывает значение переменной counter к нулю и в цикле последовательно увеличивает ее значение до 5. В функции main запускается четыре горутин work. Но какой в данном случае будет консольный вывод? Он может быть, например, таким:

Goroutine 3 - 1
Goroutine 3 - 2
Goroutine 3 - 3
Goroutine 3 - 4
Goroutine 3 - 5
Goroutine 2 - 1
Goroutine 2 - 6
Goroutine 2 - 7
Goroutine 2 - 8
Goroutine 2 - 9
Goroutine 1 - 1
Goroutine 1 - 10
Goroutine 1 - 11
Goroutine 1 - 12
Goroutine 1 - 13
Goroutine 4 - 1
Goroutine 4 - 14
Goroutine 4 - 15
Goroutine 4 - 16
Goroutine 4 - 17
The End

Несмотря на то, что в каждой горутине значение counter сбрасывается к 0, а затем увеличивается до 5, мы видим, что несколько горутин после сброса переменной работают совсем с другим значением. То есть при запуске горутин каждая из них получает значение переменной counter и начинает с ней работать. Пока одна горутина еще не закончила работу с counter в цикле, с этой же переменной начинает работать и другая горутина. То есть к одному и тому же разделяемому общему ресурсу - переменной counter одновременно работают сразу несколько горутин. Это может привести к некорректным результатам, как в данном случае.

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

package main
import (
"fmt"
"sync"
)

var counter int = 0				//	общий ресурс
func main() {

	ch := make(chan bool)		// канал
	var mutex sync.Mutex		// определяем мьютекс
	for i := 1; i < 5; i++{
		go work(i, ch, &mutex)
	}
	
	for i := 1; i < 5; i++{
		<-ch
	}
	
	fmt.Println("The End")
}
func work (number int, ch chan bool, mutex *sync.Mutex){
	mutex.Lock()	// блокируем доступ к переменной counter
	counter = 0
	for k := 1; k <= 5; k++{
		counter++
		fmt.Println("Goroutine", number, "-", counter)
	}
	mutex.Unlock()	// деблокируем доступ
	ch <- true
}

Теперь функция work принимает указатель на мьютекс. С помощью вызова mutex.Lock() мьютекс блокируется данной горутиной. Это значит, что к последующему коду имеет доступ только та горутина, которая первая заблокировала мьютекс. Остальные горутины ждут пока, мьютекс освободится.

Далее горутина сбрасывает значение переменной counter к нулю и затем в цикле последовательно увеличивает его. В конце, когда все действия с общим ресурсом уже выполнены, горутина освобождает мьютекс с помощью вызова mutex.Unlock(). Ожидающие горутины получают сигнал, что мьютекс освободился, и одна из горутин блокирует мьютекс и начинает выполнять действия с переменной counter. И так далее горутины последовательно захватывают и освобождают мьютекс. В итоге к следующей секции:

mutex.Lock()	// блокируем доступ к переменной counter
counter = 0
for k := 1; k <= 5; k++{
	counter++
	fmt.Println("Goroutine", number, "-", counter)
}
mutex.Unlock()	// деблокируем доступ

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

Goroutine 1 - 1
Goroutine 1 - 2
Goroutine 1 - 3
Goroutine 1 - 4
Goroutine 1 - 5
Goroutine 4 - 1
Goroutine 4 - 2
Goroutine 4 - 3
Goroutine 4 - 4
Goroutine 4 - 5
Goroutine 3 - 1
Goroutine 3 - 2
Goroutine 3 - 3
Goroutine 3 - 4
Goroutine 3 - 5
Goroutine 2 - 1
Goroutine 2 - 2
Goroutine 2 - 3
Goroutine 2 - 4
Goroutine 2 - 5
The End
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850