Что такое конкурентность (concurrency) в программировании?

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

Ответ

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

Конкурентность vs. Параллелизм:

  • Конкурентность — это дизайн системы, структура программы, способная работать над несколькими задачами одновременно. Она возможна даже на одном ядре CPU за счет переключения контекста.
  • Параллелизм — это реальное одновременное выполнение задач на нескольких ядрах или процессорах. Параллелизм — это один из способов реализации конкурентности.

Пример конкурентности на одном ядре (асинхронный код C#):

public async Task ServeMultipleClientsAsync()
{
    // Запускаем три "долгих" I/O задачи конкурентно (например, запросы к БД или API).
    // Они не выполняют вычисления на CPU, а ждут ответа от внешних ресурсов.
    Task<int> dbQueryTask = QueryDatabaseAsync();
    Task<string> apiCallTask = CallExternalApiAsync();
    Task<FileStream> fileReadTask = ReadLargeFileAsync();

    // Ожидаем завершения всех задач. Пока одна задача "ждет", поток может быть
    // использован для обработки других задач или запросов.
    await Task.WhenAll(dbQueryTask, apiCallTask, fileReadTask);

    Console.WriteLine($"Results: {dbQueryTask.Result}, {apiCallTask.Result}");
}

private async Task<int> QueryDatabaseAsync()
{
    await Task.Delay(1000); // Имитация долгого I/O-операции
    return 42;
}

Основные проблемы конкурентности и их решения:

  1. Состояние гонки (Race Condition): Неопределенный результат из-за порядка выполнения потоков.
    • Решение: Использование блокировок (lock, Mutex), потокобезопасных коллекций, неизменяемых (immutable) структур данных.
  2. Взаимная блокировка (Deadlock): Два или более потока бесконечно ждут друг друга.
    • Решение: Упорядочивание захвата блокировок, использование Monitor.TryEnter с таймаутом, CancellationToken.
  3. Голодание (Starvation): Поток не может получить доступ к общему ресурсу.
    • Решение: Справедливые (fair) примитивы синхронизации, правильный дизайн приоритетов.

Модели реализации конкурентности в .NET:

  • Многопоточность (Threads): Низкоуровневая модель. ThreadPool для эффективного управления потоками.
  • Асинхронное программирование (async/await): Модель, идеально подходящая для I/O-операций, позволяющая освобождать потоки во время ожидания.
  • Параллельные циклы (Parallel.For, PLINQ): Для распараллеливания CPU-связанных задач.
  • Каналы (Channels, System.Threading.Channels): Для реализации шаблона Producer-Consumer с высокой пропускной способностью.