Синхронизация и безопасность в Go vs Java | Сoncurrency часть 2

Во второй части мы углубимся в синхронизацию и безопасность параллельного кода в Go. Для Java-разработчика полезно видеть аналоги: synchronized, ReentrantLock, CountDownLatch, атомарные операции и схемы работы с потоками. Рассмотрим основные инструменты Go: Mutex, RWMutex, WaitGroup, атомарные операции, race conditions, deadlocks и паттерны fan-in / fan-out.

1. Mutex / RWMutex

Mutex — механизм блокировки, который позволяет защищать доступ к общим ресурсам между goroutine. RWMutex позволяет разделять блокировку на чтение и запись: несколько читателей могут работать одновременно, но запись блокирует всех.


// Go: Mutex
var m sync.Mutex
counter := 0

m.Lock()
counter++
m.Unlock()

// RWMutex
var rw sync.RWMutex
rw.RLock()   // чтение
value := counter
rw.RUnlock()
rw.Lock()    // запись
counter = value + 1
rw.Unlock()

// Java: synchronized / ReentrantLock
ReentrantLock lock = new ReentrantLock();
int counter = 0;

lock.lock();
try {
    counter++;
} finally {
    lock.unlock();
}

// Для чтения/записи используется ReentrantReadWriteLock
ReentrantReadWriteLock rw = new ReentrantReadWriteLock();
rw.readLock().lock();
try {
    int value = counter;
} finally {
    rw.readLock().unlock();
}
rw.writeLock().lock();
try {
    counter = value + 1;
} finally {
    rw.writeLock().unlock();
}
Используйте Mutex, когда нужен строгий контроль доступа. RWMutex эффективен для случаев, когда чтений больше, чем записей.

2. WaitGroup

WaitGroup позволяет ждать завершения группы goroutine. В Java аналогично CountDownLatch или Phaser.


// Go
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
    wg.Add(1)
    go func(n int) {
        defer wg.Done()
        fmt.Println("Task", n)
    }(i)
}
wg.Wait()

// Java
CountDownLatch latch = new CountDownLatch(5);
for (int i = 0; i < 5; i++) {
    int n = i;
    new Thread(() -> {
        System.out.println("Task " + n);
        latch.countDown();
    }).start();
}
latch.await();
WaitGroup помогает безопасно ждать окончания нескольких задач. Важно не забывать делать Done() для каждой добавленной задачи.

3. Atomic operations

Для простой синхронизации счётчиков или флагов Go предоставляет пакет sync/atomic. В Java аналогично AtomicInteger и другие атомарные классы.


// Go
var counter int32 = 0
atomic.AddInt32(&counter, 1)
value := atomic.LoadInt32(&counter)

// Java
AtomicInteger counter = new AtomicInteger(0);
counter.incrementAndGet();
int value = counter.get();
Атомарные операции эффективны для простых счётчиков или флагов. Для сложных структур лучше использовать Mutex.

4. Race conditions

Race condition возникает, когда несколько goroutine одновременно изменяют общий ресурс без синхронизации. В Java аналогично многопоточные ошибки без synchronized/lock.


// Go: пример гонки
var counter int
for i := 0; i < 100; i++ {
    go func() { counter++ }()
}
fmt.Println(counter) // может быть меньше 100 из-за гонки
Всегда анализируйте потенциальные race conditions. Go имеет инструмент go run -race для обнаружения гонок.

5. Deadlocks

Deadlock возникает, когда goroutine блокируют друг друга навечно, например, из-за циклических зависимостей на Mutex или каналах.


// Go: простой deadlock
ch := make(chan int)
ch <- 1 // никто не читает — блокировка
Deadlock трудно отлавливать. Планируйте порядок захвата ресурсов и используйте buffered каналы, где возможно.

6. Fan-in / Fan-out

Fan-out — распределение задач на несколько goroutine. Fan-in — сбор результатов в один канал. Это паттерн эффективного параллельного потока данных.


// Go: Fan-out / Fan-in
jobs := make(chan int, 5)
results := make(chan int, 5)

// Fan-out
for w := 1; w <= 3; w++ {
    go func(id int) {
        for j := range jobs {
            results <- j*2
        }
    }(w)
}

// Отправляем задачи
for j := 1; j <= 5; j++ {
    jobs <- j
}
close(jobs)

// Fan-in
for a := 1; a <= 5; a++ {
    fmt.Println(<-results)
}
Fan-in / Fan-out позволяет масштабировать обработку задач и собирать результаты безопасно. Используйте buffered каналы для избегания блокировки.

Итог

В этой статье мы разобрали ключевые механизмы синхронизации в Go: Mutex / RWMutex для защиты ресурсов, WaitGroup для ожидания завершения задач, атомарные операции для простых счётчиков, а также рассмотрели race conditions, deadlocks и паттерны fan-in / fan-out.

Для Java-разработчика эти конструкции знакомы по функционалу: synchronized/ReentrantLock, CountDownLatch, AtomicInteger и другие. Важно понимать, когда использовать mutex, когда атомарные операции, и как безопасно собирать результаты из множества goroutine.

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


Всего лайков:0
Мой канал в социальных сетях
Отправляя email, вы принимаете условия политики конфиденциальности

Полезные статьи:

Побитовые операторы в Java
Побитовые операторы в Java В языке программирования Java определено несколько побитовых операторов. Эти операторы применяются к целочисленным типам данных, таким как byte, short, int, long и char. Спи...
Как удержать легаси-проект от смерти и подарить ему ещё 10 лет
Признаки легаси-проекта: как распознать старый корабль Легаси — это не просто старый код. Это живой организм, который пережил десятки изменений, смену команд, устаревшие технологии и множество временн...
Struct, методы и интерфейсы в Go vs Java | Types - Language
Серия: Go для Java-разработчиков — разбираем struct, interface, receiver types и type embedding В этой статье мы разберем, как в Go строится архитектура типов. Для Java-разработчика это особенно важн...

Новые статьи:

Go ↔ Java: Полное руководство по Runtime, памяти и аллокатору - часть 3
Эта статья — комплексное руководство по ключевым аспектам работы памяти и рантайма в Go и Java. Мы разберем фундаментальные концепции: планировщик выполнения, memory barriers, выравнивание памяти, рос...
Низкоуровневые механизмы | Go ↔ Java
В этой статье мы разберем ключевые низкоуровневые механизмы Go, сравнивая их с аналогичными инструментами в Java. Статья предназначена для Java-разработчиков, которые хотят глубже понять Go, а также д...
Scheduler internals в Go ↔ Java: как на самом деле исполняется твой код
Когда ты пишешь go func() или создаёшь Thread в Java, кажется, что ты управляешь параллельностью. Но на самом деле ты даёшь задачу планировщику — scheduler у. И вот тут начинается настоящее шоу. Go ...
Fullscreen image