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

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

Как написать Hello World в Java. Что такое Statement. Как писать Комментарии Java
Сегодня мы разберем основные элементы Java: Statement (инструкции) Блоки кода Создадим простейшую программу Hello World! Разберем каждое слово в коде Научимся писать комментарии, которые не исполняют...
От микросервисной революции к эпохе эффективности
Период 2010–2020 годов можно назвать эпохой разделения и масштабирования. Системы стали слишком большими, чтобы оставаться монолитами. Решением стали микросервисы — маленькие автономные приложения, ра...
Memory / Runtime / Allocator - Go vs Java
Управление памятью, указатели и профилирование — это фундаментальные аспекты эффективного кода. Рассмотрим три ключевых концепта: slice backing array, pointer и профилирование (pprof / trace), и сравн...

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

Внутреннее устройство Garbage Collector: Go ↔ Java
В этой статье мы подробно разберём работу сборщика мусора (Garbage Collector, GC) в Go и Java, рассмотрим ключевые внутренние механизмы: concurrent mark &amp; sweep, mutator vs collector, tricolor mar...
Memory, Runtime и Allocator: Сравнение Go и Java для разработчиков
В этой статье мы разберём ключевые аспекты работы с памятью, runtime и механизмами аллокации объектов в Go и Java. Мы сфокусируемся на различиях подходов к управлению памятью, работе со стеком и кучей...
Atomic vs Mutex, Blocking vs Non‑Blocking, Read/Write Splitting (RWMutex), Logging | Concurrency Patterns и Best Practices  часть 5 | Go ↔ Java
В этой статье мы разберём ключевые подходы к работе с параллелизмом и синхронизацией в Go и Java. Мы сравним, как одни и те же задачи решаются на этих языках, покажем идиомы, паттерны и лучшие практик...
Fullscreen image