← Назад к вопросам

Какие знаешь методы синхронизации потоков?

3.0 Senior🔥 172 комментариев
#C# и ООП#Асинхронность и многопоточность

Комментарии (2)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Основные методы синхронизации потоков в C# и Unity

Как Unity Developer с многолетним опытом, я регулярно сталкиваюсь с необходимостью синхронизации потоков, особенно при работе с многопоточностью в играх — будь то загрузка ресурсов, обработка вычислений в фоне или синхронизация доступа к общим данным.

Встроенные примитивы синхронизации в C#

В экосистеме .NET и C# доступны следующие ключевые методы:

1. Блокировки (locks) Наиболее распространённый подход — использование оператора lock. Он обеспечивает взаимное исключение (mutex) для критической секции кода.

private readonly object _lockObject = new object();
private int _sharedCounter = 0;

void IncrementCounter()
{
    lock (_lockObject)
    {
        _sharedCounter++;
        // Критическая секция защищена
    }
}

2. Мьютексы (Mutex) Системные примитивы, которые могут синхронизировать потоки даже между процессами.

using System.Threading;

private static Mutex mutex = new Mutex();

void AccessSharedResource()
{
    mutex.WaitOne();
    try
    {
        // Работа с общим ресурсом
    }
    finally
    {
        mutex.ReleaseMutex();
    }
}

3. Семафоры (Semaphore и SemaphoreSlim) Ограничивают количество потоков, которые могут одновременно получить доступ к ресурсу.

// SemaphoreSlim более легковесный для внутрипроцессной синхронизации
private SemaphoreSlim semaphore = new SemaphoreSlim(3, 3); // Максимум 3 потока

async Task ProcessData()
{
    await semaphore.WaitAsync();
    try
    {
        // Работа с ограниченным ресурсом
    }
    finally
    {
        semaphore.Release();
    }
}

4. Мониторы (Monitor) Более гибкая альтернатива lock с дополнительными методами (Wait, Pulse, PulseAll).

private object syncObject = new object();

void ThreadSafeOperation()
{
    bool lockTaken = false;
    try
    {
        Monitor.Enter(syncObject, ref lockTaken);
        // Критическая секция
    }
    finally
    {
        if (lockTaken)
            Monitor.Exit(syncObject);
    }
}

5. Межпоточные события (ManualResetEvent, AutoResetEvent) Позволяют потокам ждать сигнала от других потоков.

private ManualResetEvent resetEvent = new ManualResetEvent(false);

void Thread1()
{
    // Выполнение работы
    resetEvent.Set(); // Сигнализация другому потоку
}

void Thread2()
{
    resetEvent.WaitOne(); // Ожидание сигнала
    // Продолжение выполнения
}

Специфика синхронизации в Unity

В Unity есть важные ограничения и особенности:

  • Главный поток (Main Thread): Большинство API Unity не являются потокобезопасными и должны вызываться только из главного потока.
  • Thread Safe очередь (ThreadSafeQueue): Паттерн для передачи задач в главный поток.
// Пример выполнения кода в главном потоке через очередь
using UnityEngine;
using System.Collections.Concurrent;

public class MainThreadDispatcher : MonoBehaviour
{
    private static readonly ConcurrentQueue<System.Action> 
        executionQueue = new ConcurrentQueue<System.Action>();
    
    void Update()
    {
        while (executionQueue.TryDequeue(out var action))
        {
            action?.Invoke();
        }
    }
    
    public static void ExecuteOnMainThread(System.Action action)
    {
        executionQueue.Enqueue(action);
    }
}

// Использование из другого потока
void SomeBackgroundThread()
{
    // Загрузка данных...
    MainThreadDispatcher.ExecuteOnMainThread(() => {
        // Теперь этот код выполнится в главном потоке
        GameObject.Find("Player").transform.position = Vector3.zero;
    });
}

Рекомендации для Unity разработки

  • Минимизируйте блокировки: В реальном времени игры длительные блокировки могут вызвать просадки FPS.
  • Используйте асинхронные паттерны: async/await с Task и SemaphoreSlim часто предпочтительнее тяжеловесных примитивов.
  • Избегайте deadlock: Всегда освобождайте ресурсы в finally блоке и следите за порядком захвата блокировок.
  • Профилирование: Используйте Profiler для отслеживания contention (состязания за ресурсы) между потоками.

Выбор конкретного метода синхронизации зависит от сценария: для простых операций достаточно lock, для ограничения параллелизма — семафоры, для ожидания событий — ManualResetEvent. В Unity особенно важно разделять фоновые вычисления и модификацию игровых объектов, которые должны происходить исключительно в главном потоке.