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

Приведи пример использования примитива синхронизации

2.0 Middle🔥 131 комментариев
#Асинхронность и многопоточность

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

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

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

Пример использования примитива синхронизации в C#

В C# для синхронизации доступа к общим ресурсам в многопоточных приложениях используются различные примитивы синхронизации. Я продемонстрирую практический пример с использованием Mutex (взаимоисключающая блокировка) — одного из фундаментальных примитивов синхронизации.

Контекст проблемы

Представьте ситуацию, где несколько потоков одновременно пытаются записывать данные в один файл. Без синхронизации это приведёт к состоянию гонки (race condition), повреждению данных и непредсказуемому поведению.

Пример с использованием Mutex

using System;
using System.Threading;
using System.IO;

class Program
{
    // Создаём именованный Mutex для синхронизации между потоками
    private static Mutex fileMutex = new Mutex(false, "Global\\FileWriteMutex");
    private static string filePath = "shared_log.txt";
    
    static void Main()
    {
        // Запускаем несколько потоков для записи в файл
        Thread[] threads = new Thread[5];
        
        for (int i = 0; i < threads.Length; i++)
        {
            int threadId = i + 1;
            threads[i] = new Thread(() => WriteToFile($"Thread {threadId}: Message {DateTime.Now:HH:mm:ss.fff}"));
            threads[i].Start();
            
            // Небольшая задержка для эмуляции реалистичного сценария
            Thread.Sleep(50);
        }
        
        // Ожидаем завершения всех потоков
        foreach (var thread in threads)
        {
            thread.Join();
        }
        
        Console.WriteLine("Все потоки завершили работу.");
        Console.WriteLine($"Содержимое файла {filePath}:");
        Console.WriteLine(File.ReadAllText(filePath));
        
        // Освобождаем ресурсы Mutex
        fileMutex.Close();
    }
    
    static void WriteToFile(string message)
    {
        try
        {
            // Захватываем блокировку
            fileMutex.WaitOne();
            
            Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} получил доступ к файлу");
            
            // Критическая секция - запись в файл
            using (StreamWriter writer = File.AppendText(filePath))
            {
                writer.WriteLine(message);
                writer.Flush();
                
                // Имитация обработки
                Thread.Sleep(100);
            }
            
            Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} завершил запись");
        }
        finally
        {
            // Всегда освобождаем блокировку
            fileMutex.ReleaseMutex();
        }
    }
}

Ключевые моменты реализации

  1. Инициализация Mutex:

    • Создаём именованный Mutex с префиксом Global\\, что позволяет синхронизировать процессы в рамках всей системы
    • Без имени Mutex будет работать только в рамках одного процесса
  2. Критическая секция:

    • WaitOne() блокирует текущий поток до получения доступа к ресурсу
    • Операции внутри блока try-finally защищены от одновременного выполнения
    • ReleaseMutex() освобождает блокировку для других потоков
  3. Обработка ошибок:

    • Использование try-finally гарантирует освобождение Mutex даже при возникновении исключений
    • Без этого дедлок (взаимная блокировка) был бы неизбежен

Альтернативные примитивы синхронизации

Помимо Mutex, в C# доступны:

  • lock (монитор) — для синхронизации в пределах одного процесса:
private static readonly object lockObject = new object();

lock(lockObject)
{
    // Критическая секция
}
  • Semaphore — ограничивает количество одновременных доступов:
private static SemaphoreSlim semaphore = new SemaphoreSlim(3, 3);

await semaphore.WaitAsync();
try { /* работа с ресурсом */ }
finally { semaphore.Release(); }
  • ReaderWriterLockSlim — оптимизирован для сценариев с частым чтением:
private static ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim();

// Для чтения
rwLock.EnterReadLock();
try { /* чтение данных */ }
finally { rwLock.ExitReadLock(); }

// Для записи
rwLock.EnterWriteLock();
try { /* изменение данных */ }
finally { rwLock.ExitWriteLock(); }

Практические рекомендации

  1. Минимизируйте время удержания блокировки — выполняйте в критической секции только необходимые операции
  2. Избегайте вложенных блокировок — это частая причина дедлоков
  3. Используйте try-finally для гарантированного освобождения ресурсов
  4. Выбирайте примитив под задачуMutex для межпроцессного взаимодействия, lock для внутрипроцессной синхронизации
  5. Рассмотрите асинхронные альтернативыSemaphoreSlim.WaitAsync() для асинхронного кода

Данный пример демонстрирует базовый паттерн использования Mutex для защиты разделяемого ресурса от конкурентного доступа, что является фундаментальным аспектом разработки многопоточных приложений на C#.

Приведи пример использования примитива синхронизации | PrepBro