В чем разница между многопоточностью (multithreading) и асинхронностью?

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

Ответ

Многопоточность и асинхронность — это два разных подхода к параллельному выполнению задач, решающих разные проблемы.

Многопоточность (Multithreading)

  • Цель: Параллельное выполнение CPU-bound задач (тяжёлые вычисления) за счёт использования нескольких потоков ОС.
  • Как работает: Операционная система планирует выполнение потоков на ядрах процессора. Потоки разделяют память процесса.
  • Основные проблемы:
    • Гонки данных (Race Conditions): Несколько потоков одновременно обращаются к общим данным.
    • Взаимные блокировки (Deadlocks): Потоки бесконечно ждут ресурсы, захваченные друг другом.
    • Накладные расходы: Создание потоков и переключение контекста между ними — дорогие операции.
  • Когда использовать: Для распараллеливания вычислений (например, обработка изображений, сложные математические расчёты).

Асинхронность (Async/Await)

  • Цель: Эффективное выполнение I/O-bound задач (работа с сетью, файлами, БД) без блокировки потока.
  • Как работает: Один поток освобождается во время ожидания операции ввода-вывода (например, ответа от API) и может выполнять другую работу. После завершения I/O выполнение продолжается (часто на том же потоке).
  • Основные преимущества:
    • Высокая масштабируемость для I/O операций.
    • Нет проблем с синхронизацией потоков, так как часто работает в одном потоке.
    • Меньшие накладные расходы по сравнению с созданием множества потоков.
  • Когда использовать: Для сетевых запросов, работы с файловой системой, вызовов API — везде, где есть ожидание.

Ключевые отличия и пример

Аспект Многопоточность Асинхронность
Решаемая проблема Параллелизм вычислений (CPU-bound) Эффективность при ожидании (I/O-bound)
Использование ресурсов Создаёт и использует несколько потоков ОС Может эффективно работать в одном потоке
Типичные проблемы Гонки данных, deadlocks Неправильное использование (async без await, deadlock на .Result)

Пример асинхронного кода в C#:

public async Task<string> DownloadDataAsync(string url)
{
    using (var httpClient = new HttpClient())
    {
        // Поток освобождается во время ожидания сетевого запроса
        string data = await httpClient.GetStringAsync(url);
        return ProcessData(data); // Выполняется после получения ответа
    }
}

Комбинирование подходов: Часто их используют вместе. Например, асинхронный метод может запускать CPU-задачу в отдельном потоке с помощью Task.Run() для неблокирующего выполнения тяжёлых вычислений.