Что такое многопоточность (Multithreading)?

«Что такое многопоточность (Multithreading)?» — вопрос из категории Многопоточность, который задают на 62% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Многопоточность — это техника выполнения нескольких потоков (threads) внутри одного процесса операционной системы для параллельного или псевдопараллельного решения задач. Каждый поток имеет свой собственный стек вызовов, но разделяет с другими потоками процесса общую память (кучу) и ресурсы (открытые файлы, сокеты).

Зачем это нужно?

  1. Повышение производительности (CPU-bound задачи): Использование нескольких ядер процессора для одновременных вычислений (например, обработка изображений, сложные расчёты).
  2. Отзывчивость приложений (UI): Выполнение длительных операций (загрузка файла, запрос к БД) в фоновом потоке, чтобы не блокировать главный поток интерфейса.
  3. Эффективное использование ресурсов (I/O-bound задачи): Пока один поток ожидает ответа от диска или сети, другие потоки могут продолжать работу.

Основные абстракции для работы с потоками в C# / .NET:

  • Thread (System.Threading): Низкоуровневая абстракция потока ОС. Прямое создание и управление сейчас используется реже из-за высоких накладных расходов.
  • ThreadPool: Пул готовых к работе потоков, управляемый CLR. Эффективен для коротких задач. Не рекомендуется использовать для долгих блокирующих операций.
  • Task / Task Parallel Library (TPL) (System.Threading.Tasks): Основная современная абстракция. Task представляет собой асинхронную операцию, которая может выполняться в потоке из пула.
    // Запуск задачи в пуле потоков
    Task.Run(() =>
    {
        Console.WriteLine($"Выполняюсь в потоке с ID: {Thread.CurrentThread.ManagedThreadId}");
        // Длительная CPU-задача
        for (int i = 0; i < 10; i++)
        {
            Thread.Sleep(100); // Имитация работы
            Console.WriteLine(i);
        }
    });
  • async / await: Ключевые слова для написания асинхронного кода, который легко читается как синхронный. await не блокирует поток, а освобождает его, пока выполняется асинхронная операция (например, I/O).
    public async Task<string> DownloadDataAsync(string url)
    {
        using var httpClient = new HttpClient();
        // Поток освобождается здесь, пока идёт загрузка
        string data = await httpClient.GetStringAsync(url);
        // Продолжение выполнится на любом доступном потоке из пула
        return ProcessData(data);
    }
  • Parallel класс: Для простого параллельного выполнения циклов (For, ForEach) или операций (Invoke).
    Parallel.For(0, 100, i =>
    {
        DoWork(i); // Метод будет вызван параллельно для разных i
    });

Критические проблемы и их решение:

  • Гонка данных (Race Condition): Когда несколько потоков одновременно обращаются к общим данным и хотя бы один пишет. Решение: Использование примитивов синхронизации.
  • Взаимная блокировка (Deadlock): Два или более потока бесконечно ждут друг друга. Решение: Упорядоченный захват блокировок, использование таймаутов (Monitor.TryEnter).
  • Синхронизация: Для защиты общих ресурсов используются:
    • lock (ключевое слово, обёртка над Monitor).
    • Mutex, Semaphore, SemaphoreSlim — для межпроцессной или более сложной синхронизации.
    • Concurrent коллекции (ConcurrentBag<T>, ConcurrentDictionary<TKey,TValue>) — потокобезопасные коллекции.
Поток (Thread) vs Задача (Task): Аспект Thread Task
Уровень Низкий (поток ОС) Высокий (работа над потоком)
Ресурсы Дорого (1 МБ стека) Дёшево (работает через ThreadPool)
Управление Ручное Автоматическое (планировщик задач)
Результат Нет встроенного Возвращает результат (Task<T>)
Отмена Сложная Встроенная (CancellationToken)
Современный подход Редко Рекомендуется

Видео-ответы