Вопросы и ответы на собеседовании по Swift IOS. Часть 3. Многопоточность и параллелизм

Часть 3 состоит из вопросов и ответов для интервью Swift iOS по потоку и параллелизму. Он будет охватывать темы, связанные с параллелизмом, такие как GCD, NSOperation, очередь операций, блокировка, семафор и т. д. Многопоточность и параллелизм являются важными концепциями в разработке программного обеспечения и особенно актуальны в мобильной разработке, где основными проблемами являются ограниченная вычислительная мощность и время автономной работы. По этой причине их часто включают в технические интервью для старших разработчиков iOS.

В разработке iOS понимание многопоточности и параллелизма имеет решающее значение для создания отзывчивых и эффективных приложений. Разработчик iOS должен быть знаком с различными инструментами и методами, доступными для управления параллельными задачами, такими как Grand Central Dispatch (GCD) и Operation Queues, и знать, как использовать их для оптимизации производительности и скорости отклика приложения. Способность понимать и устранять условия гонки, взаимоблокировки и другие проблемы с синхронизацией — важный навык для старшего разработчика iOS. Это включает в себя понимание использования блокировок, семафоров и других механизмов синхронизации, а также влияние многопоточного программирования на общую архитектуру и дизайн приложения. Вопрос о многопоточности и параллелизме на собеседовании на должность разработчика iOS предназначен для оценки способности кандидата создавать производительные и масштабируемые приложения и справляться со сложностями, возникающими при многопоточном программировании.

Что такое GCD?

GCD — это библиотека, которая предоставляет низкоуровневый объектно-ориентированный API для одновременного выполнения задач при управлении потоками за кулисами. GCD абстрагирует код управления потоками и перемещает его на системный уровень, предоставляя легкий API для определения задач и их выполнения в соответствующей очереди отправки.

Что такое очередь отправки?

Очередь отправки отвечает за выполнение задачи в порядке FIFO.

Объясните основной поток и его использование

Основной поток в iOS относится к основному потоку выполнения приложения. Он отвечает за обработку событий и обновлений пользовательского интерфейса, а также за выполнение важных задач, таких как обновление анимации и обработка пользовательского ввода. В iOS все обновления и события, связанные с пользовательским интерфейсом, должны происходить в основном потоке.

Это связано с тем, что платформа UIKit, которая предоставляет основные компоненты для создания пользовательских интерфейсов в iOS, не является потокобезопасной и доступна только из основного потока. Это гарантирует, что пользовательский интерфейс остается отзывчивым и стабильным даже при выполнении сложных фоновых задач.

Правильное использование основного потока имеет решающее значение для стабильности и производительности приложения iOS. Если основной поток будет заблокирован в течение длительного периода времени, пользовательский интерфейс может перестать отвечать на запросы, и приложение может даже аварийно завершить работу. Поэтому важно свести к минимуму объем работы, выполняемой в основном потоке, и использовать фоновые потоки для задач, интенсивно использующих ЦП.

Что такое serial queue?

Последовательная очередь может запускать задачи по одной, и ей нужно дождаться завершения запущенных задач.

Что такое concurrent queue?

Параллельная очередь может запускать столько задач, сколько может, не дожидаясь завершения запущенных задач.

Объясните очереди GCD

GCD (Grand Central Dispatch) — это технология, используемая в операционных системах Apple macOS и iOS для управления одновременными операциями. Он предоставляет способ асинхронного запуска задач в разных потоках и помогает управлять отправкой задач на разные ресурсы обработки.

Очереди GCD являются основным механизмом выполнения задач в GCD. Их можно рассматривать как набор задач, которые необходимо выполнить, и они используются для управления порядком выполнения задач и количеством задач, которые могут выполняться одновременно. Задачи могут быть добавлены в очередь, а затем очередь выполняет эти задачи в том порядке, в котором они были добавлены. В GCD есть два типа очередей: последовательные и параллельные. Последовательная очередь запускает задачи по одной в том порядке, в котором они были добавлены, в то время как параллельная очередь может запускать несколько задач одновременно.

GCD предоставляет три экземпляра очередей, которые используются в iOS.

Основная очередь: последовательная очередь — выполняется в основном потоке.

Глобальная очередь: Параллельная очередь. Она работает с разными приоритетами и является общей для всей системы.

Пользовательская очередь: последовательная/параллельная очередь.

Что такое Quality of Service GCD?

Качество обслуживания является приоритетом для выполнения задачи в GCD. Если задача имеет более высокое качество обслуживания, чем другие, она будет обработана до более низкого качества обслуживания.

Качество услуг ранжируется от высшего к низшему –

User Interactive: работа, которая происходит в основном потоке, например анимация или операции рисования.

User Initiated: работа, которую запускает пользователь и которая должна дать немедленные результаты. Эта работа должна быть завершена, чтобы пользователь мог продолжить.

Utility: работа, которая может занять немного времени и не требует немедленного завершения. Аналогично индикаторам выполнения и импорту данных.

Background: Эта работа не видна пользователю. Резервное копирование, синхронизация, индексация и т. д.

Что такое состояние гонки?

Состояние гонки — это ситуация, в которой поведение программной системы зависит от относительного времени событий, например от порядка выполнения потоков или задач. В iOS состояние гонки может возникнуть, когда несколько потоков или задач пытаются одновременно получить доступ к одним и тем же данным или изменить их, что приводит к неожиданным или неверным результатам.

Например, представьте, что два потока пытаются увеличить одну и ту же переменную. Если оба потока обращаются к переменной одновременно, увеличивают ее значение, а затем сохраняют результат, переменная может получить неверное значение, поскольку порядок выполнения приращений не гарантируется. Это пример состояния гонки.

Условия гонки бывает сложно обнаружить и отладить, поскольку они могут возникать непоследовательно или предсказуемо. Чтобы избежать условий гонки в iOS, важно использовать методы синхронизации, такие как блокировки, семафоры или атомарные операции, чтобы обеспечить контролируемый доступ к данным и их изменение.

Что такое инверсия приоритета?

Инверсия приоритета — это критическое состояние многопоточности, когда поток с низким приоритетом блокирует выполнение потока с высоким приоритетом и делает назначенные приоритеты бессмысленными для потока.

Что такое deadlock?

Взаимная блокировка возникает, когда две или иногда более задачи ждут завершения другой, и ни одна из них никогда этого не делает.

Что такое async-await в Swift?

Async-await — это шаблон программирования, который позволяет вам писать асинхронный код в более читабельном и лаконичном виде в Swift. Он был представлен в Swift 5.5 и построен на основе существующих функций параллелизма Swift.

С помощью async-await вы можете писать асинхронный код, как если бы он был синхронным. Это означает, что вы можете написать код, который выглядит и ведет себя как обычный синхронный код, но на самом деле он выполняется асинхронно.

func fetchData() async throws -> Data {
  let url = URL(string: "https://example.com/data")!
  let (data, _) = try await URLSession.shared.data(from: url)
  return data
}

func printData() async {
  do {
    let data = try await fetchData()
    print(String(data: data, encoding: .utf8)!)
  } catch {
    print("Error: \(error)")
  }
}

Task { 
  await printData() 
}

Здесь у нас есть две функции: fetchData и printData. fetchData — это асинхронная функция, которая извлекает данные из URL-адреса с помощью URLSession. printData также является асинхронной функцией, которая вызывает fetchData и печатает результат. Чтобы вызвать асинхронную функцию, мы используем ключевое слово await. Когда мы вызываем fetchData из printData, мы используем await, чтобы дождаться завершения функции, прежде чем продолжить выполнение. Если возникает ошибка, мы обрабатываем ее с помощью блока do-catch.

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

Какие проблемы решают фреймворки async-await и Combine?

Фреймворки Async-await и Combine решают похожие проблемы, а именно обработку асинхронных операций. Однако async-await предлагает более лаконичный и читаемый способ написания асинхронного кода, а Combine предоставляет мощную среду реактивного программирования, которую можно использовать для обработки потоков данных.

Одной из основных проблем, которую решает async-await в контексте реализации на основе обратного вызова, является проблема ада обратного вызова. В традиционном коде, основанном на обратных вызовах, обычно используются вложенные обратные вызовы, которые могут быстро стать трудными для чтения и обслуживания.

С помощью async-await вы можете писать асинхронный код, который выглядит и ведет себя как синхронный код, избегая необходимости во вложенных обратных вызовах и улучшая читаемость кода.

С другой стороны, Combine обеспечивает способ обработки потоков данных более реактивным способом. Вместо того, чтобы делать явные обратные вызовы для обновления пользовательского интерфейса или обработки данных, Combine предоставляет набор операторов, которые позволяют вам манипулировать потоками данных и преобразовывать их по мере их создания.

Это может упростить выполнение сложных асинхронных операций, таких как обработка сетевых запросов или обработка больших объемов данных.

Подводя итог, можно сказать, что async-await и Combine — это два варианта управления асинхронными задачами в Swift, но они решают разные задачи. Async-await представляет более четкий и компактный подход к написанию асинхронного кода, тогда как Combine представляет инфраструктуру реактивного программирования для управления потоками данных.

Что такое параллелизм и разделение времени?

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

Что такое NSOperation?

NSOperation построен на основе GCD. При использовании NSOperation разработчик может добавлять зависимости между различными операциями и повторно использовать, отменять и приостанавливать операции. Конечно, то же самое можно заархивировать и через GCD, но это добавит дополнительных накладных расходов.

Что такое NSBlockOperation?

NSBlockOperation помогает реализовать NSOperation из одного или нескольких замыканий. NSBlockOperations может иметь несколько блоков, которые могут выполняться одновременно.

Что такое ThreadPool?

ThreadPool — это пул потоков, который повторно использует фиксированное количество потоков для выполнения конкретной задачи.

Что используется sleep a thread?

sleep(forTimeInterval:) приостанавливает поток на заданный интервал времени.

Объясните concurrency против parallelism

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

Parallelism — это когда задачи выполняются буквально одновременно.

Это выступление Роба Пайка очень полезно для понимания концепции.

Что такое семафор?

Семафоры дают нам возможность контролировать доступ к общему ресурсу несколькими потоками. Семафор состоит из очереди потоков и значения счетчика. Значение счетчика используется семафором, чтобы решить, должен ли поток получить доступ к общему ресурсу или нет. Значение счетчика изменяется, когда мы вызываем функции signal() или wait().

Что такое Task?

Task представлена ​​в среде параллелизма на WWDC 2021. С помощью задачи мы можем просто создать параллельный контекст из непараллельного метода, вызывая методы с помощью async/await. Между очередями отправки и задачами есть много общего, и задачи кажутся более удобными для разработчиков с меньшим количеством кода. И задача, и очередь отправки могут обслуживать некоторую работу в другом потоке с приоритетом по умолчанию/определяемым пользователем.

Что такое Actor?

Actor в Swift работает как потокобезопасный класс, который можно безопасно использовать в параллельной среде. Компилятор Swift гарантирует, что ни один участок кода из другого потока не сможет одновременно получить доступ к данным Actor. Этот подход обеспечивает безопасность по умолчанию без написания шаблонного кода с использованием семафора/блокировки.

Что такое TaskGroup?

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

Уважаемые разработчики iOS, имейте в виду

При подготовке к собеседованию важно помнить о нескольких ключевых моментах. Во-первых, стоит отметить, что в разделе интервью доступно более 1000 вопросов для интервью, которые вы можете просмотреть и подготовиться. Читая вопрос, найдите время, чтобы тщательно обдумать свой ответ и обдумать информацию, которую вы хотите передать. Ответ, представленный здесь, в этом блоге, можно объяснить по-другому. Вы также должны подготовить свои примеры.

Также важно помнить, что эти вопросы на собеседовании не должны быть трудными. Интервьюер не хочет бросить вам вызов, а хочет начать разговор, который позволит вашим способностям и интересам выйти на первый план. Они хотят лучше узнать вас и ваш опыт.

Наконец, очень важно избегать простых ответов «да» или «нет». Интервьюеры ищут более подробные ответы, которые включают базовое понимание, рассуждение, объяснение и примеры. Итак, постарайтесь уточнить свои ответы и предоставить конкретную актуальную информацию в поддержку вашего ответа. Это продемонстрирует вашу внимательность и покажет интервьюеру, что вы хорошо подготовились к интервью.