Синхронизация и безопасность в 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, вы принимаете условия политики конфиденциальности

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

Многопоточность в Go и Java: типы задач и паттерны решения
Многопоточность — это не просто «запустить миллион потоков и пусть считают». Это искусство эффективно использовать ресурсы процессора и памяти, безопасно обрабатывать данные и правильно распределять з...
Java v25: выбор подходящей многопоточности для любых задач
Введение Мир Java стремительно развивается, и с каждой версией появляются новые инструменты для эффективной работы с многопоточностью, коллекциями и асинхронностью. В Java 25 разработчики получают мощ...
Условные операторы в Java
Java — Условные операторы Наглядная статья с примерами: if / else / логика / тернарный оператор / switch Кратко — условные операторы позволяют программе принимать решения: выполнить один кусок кода ...

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

Resource cleanup, rate‑limiting strategies, bounded vs unbounded channels - в Go vs Java | Паттерны, идиомы и лучшие практики Go
Продолжаем серию статей для разработчиков, которые хотят изучить Go на основе знаний Java, и наоборот. В этой статье мы обсудим три ключевые темы: Resource Cleanup (освобождение ресурсов), Rate-Limiti...
Разбираем: Rate‑limiter, non‑blocking operations, scheduler  Go vs Java | Concurrency часть 4
Эта статья посвящена пониманию принципов работы с конкурентностью и синхронизацией в Go и Java. Мы рассмотрим ключевые подходы, такие как rate‑limiter, неблокирующие операции и планирование задач, сра...
Разбираем: Trace, Profiling, Integration Testing, Code Coverage, Mocking, Deadlock Detection в Go vs Java | Testing, Debugging и Profiling
Серия: Go для Java-разработчиков — разбор trace, профилирования и тестирования В этой статье мы разберем инструменты и практики для тестирования, отладки и профилирования в Go. Для Java-разработчика ...
Fullscreen image