логотип PurpleSchool
логотип PurpleSchool

Механизмы синхронизации в Golang

Автор

Олег Марков

Введение

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

В этой статье мы рассмотрим основные механизмы синхронизации, доступные в Go, такие как sync.Mutex, sync.RWMutex, sync.WaitGroup, каналы и другие. Разберем их применение и особенности, чтобы вы могли эффективно использовать их в своих проектах.

Мьютексы

Мьютекс (Mutex)

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

var mu sync.Mutex
var counter int

func increment() {
    mu.Lock()
    defer mu.Unlock()
    counter++
}

В этом примере используем mu.Lock() для блокировки доступа к переменной counter, чтобы только одна горутина могла изменять ее состояние в данный момент времени. defer mu.Unlock() гарантирует, что мьютекс будет освобожден после завершения функции, даже если в ней произойдет ошибка.

RWMutex

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

var rw sync.RWMutex
var sharedData map[string]string

func read(key string) string {
    rw.RLock()
    defer rw.RUnlock()
    return sharedData[key]
}

func write(key, value string) {
    rw.Lock()
    defer rw.Unlock()
    sharedData[key] = value
}

Здесь в read() используется RLock(), чтобы позволить нескольким горутинам одновременно считывать данные, в то время как write() использует Lock(), чтобы только одна горутина имела возможность выполнять запись.

WaitGroup

sync.WaitGroup помогает горутинам ожидать завершения других горутин. Можно сказать, что WaitGroup это счетчик активных горутин.

var wg sync.WaitGroup

func worker(id int) {
    defer wg.Done()
    fmt.Printf("Worker %d starting\n", id)
    time.Sleep(time.Second)
    fmt.Printf("Worker %d done\n", id)
}

wg.Add(3)
for i := 1; i <= 3; i++ {
    go worker(i)
}
wg.Wait()

Здесь wg.Add(3) добавляет три горутины в WaitGroup. Каждая горутина выполняет свою работу и вызывает defer wg.Done() по завершению. Основная горутина ждет, пока все рабочие горутины не будут завершены вызовом wg.Wait().

Каналы

Односторонние каналы

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

func sum(s []int, c chan int) {
    sum := 0
    for _, v := range s {
        sum += v
    }
    c <- sum // sending sum to channel c
}

s := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x, y := <-c, <-c // receive from c
fmt.Println(x, y, x+y)

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

Выборка между каналами

select это мощная конструкция Go, позволяющая пытаться получать или отправлять данные на несколько каналов одновременно.

func fibonacci(c, quit chan int) {
    x, y := 0, 1
    for {
        select {
        case c <- x:
            x, y = y, x+y
        case <-quit:
            fmt.Println("quit")
            return
        }
    }
}

c := make(chan int)
quit := make(chan int)
go func() {
    for i := 0; i < 10; i++ {
        fmt.Println(<-c)
    }
    quit <- 0
}()
fibonacci(c, quit)

Здесь select позволяет передавать и завершать вычисления в зависимости от сигналов из каналов c и quit.

Context

Пакет context используется для управления сроком жизни горутин, облегчая отмену и передачу значений между ними.

ctx, cancel := context.WithCancel(context.Background())

go func(ctx context.Context) {
    for {
        select {
        case <-ctx.Done():
            return
        default:
            fmt.Println("Running...")
            time.Sleep(500 * time.Millisecond)
        }
    }
}(ctx)

time.Sleep(2 * time.Second)
cancel()

В этом примере мы используем context.WithCancel, чтобы остановить горутину через 2 секунды.

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

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

Стрелочка влевоИспользование pprof в GolangРабота с пакетом S3 в GolangСтрелочка вправо

Все гайды по Golang

Работа с YAML в GolangПреобразование типов в GolangКонвертация структур в JSON в GolangStrconv в GolangИспользование пакета SQLx для работы с базами данных в GolangРазбираемся с SQL в GolangРазделение строк с помощью функции split в GolangSort в GoПоиск и замена строк в Go - GolangИспользование пакета reflect в GolangРабота с PostgreSQL в GoPointers в GolangПарсинг в GoРабота со списками (list) в GolangПреобразование int в string в GolangРабота с числами с плавающей точкой в GolangРабота с полями в GolangИспользование enum в GolangОбработка JSON в GoЧтение и запись CSV-файлов в GolangРабота с cookie в GolangРегистры в GoКэширование данных в GolangПреобразование byte в string в GolangByte в GoИспользование bufio для работы с потоками данных в GolangДобавление данных и элементов (add) в Go
Логирование в Golang. Zap, Logrus, Loki, GrafanaРабота с Docker-контейнерами в GoИспользование pprof в GolangМеханизмы синхронизации в GolangРабота с пакетом S3 в GolangМониторинг Golang приложений с помощью PrometheusОптимизация проектов на GoПаттерны проектирования в GolangМиграции базы данных в GolangОркестрация контейнеров Go с Kubernetes + DockerGjGo Playground и компилятор GolangИспользование go mod init для создания модулей GolangРабота с переменными окружения (env) в GolangКоманда go build в GolangАвтоматизация Golang проектов — CI/CD с GitLab CI и JenkinsОтладка кода в GolangЧтение и использование конфигурации в приложениях на GolangКомпиляция в GolangКак развернуть Go-приложение на облаке AWSАутентификация в Golang
Сетевые протоколы в GoПеременные в GolangЗначения в GolangДженерик %T и его применение в GolangТипы данных в GolangИспользование tls в GolangИспользование tag в структурах GolangSwitch в GoСтроки в GolangРабота с потоками (stream) в GolangSelect в GoРуны в GoРабота с пакетом params в GolangКонвертация строк в числа в GolangNull, Nil, None, 0 в GoНаименования переменных, функций и структур в GoInt в GolangУстановка GolangЧтение и установка HTTP заголовков в GolangMethods в GolangGoLand — IDE для разработки на Golang от JetBrainsОбработка «not found» в GolangFloat в GolangФлаги командной строки в Go (Golang)Запуск внешних команд в GolangОбработка ошибок в GoИспользование defer в GolangЗначения default в GolangГенерация кода в GoФорматирование кода в GolangЧистая архитектура в GolangКаналы (channels) в GolangПолучение body из HTTP запроса в Golang
Открыть базу знаний