Синхронизация и безопасность в 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.


🌐 in English
Всего лайков:0

Оставить комментарий

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

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

Современный подход к параллелизму в Java - Fork/Join Framework, CompletableFuture и виртуальные потоки (Project Loom)
Предисловие Мир программного обеспечения уже давно перестал быть спокойным океаном: сегодня это бурная экосистема, где каждая миллисекунда отклика приложения может стоить компании клиентов, репутации ...
Асинхронность в Java: Future, CompletableFuture и Structured Concurrency
Java изначально была ориентирована на многопоточность и параллельные вычисления. Со временем появились разные способы работы с результатами асинхронных задач — от классического Future до современных S...
Внутреннее устройство Garbage Collector: Go ↔ Java
В этой статье мы подробно разберём работу сборщика мусора (Garbage Collector, GC) в Go и Java, рассмотрим ключевые внутренние механизмы: concurrent mark &amp; sweep, mutator vs collector, tricolor mar...

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

Конкурентность — это не про «запустить много потоков». Это про договорённости между ними. Представь кухню ресторана: — повара (потоки / горутины) — заказы (задачи) — и главный вопрос: как они коорди...
История начинается не с академической теории, а с типичной production-проблемы. Представьте сервис: 48 CPU 300+ потоков нагрузка 200k операций в секунду много shared state Команда использует обы...
Когда HashMap начинает убивать продакшн: инженерная история ConcurrentHashMap
Представьте обычный продакшн-сервис. 32 CPU сотни потоков кэш конфигурации / сессий / rate limits десятки тысяч операций в секунду И где-то внутри — обычный Map. Сначала всё выглядит безобидно. Map&...
Fullscreen image