На мой взгляд, это невероятный вопрос для собеседования - по крайней мере, предполагая, что (1) кандидат должен иметь глубокие знания о потоках, и (2) интервьюер также обладает глубокими знаниями и использует этот вопрос для проверки кандидата. Всегда возможно, что интервьюер искал конкретный, узкий ответ, но компетентный интервьюер должен искать следующее:
- Способность отличать абстрактные концепции от конкретной реализации. Я добавляю это прежде всего как мета-комментарий к некоторым комментариям. Нет, обрабатывать один список слов таким способом не имеет смысла. Тем не менее, важна абстрактная концепция конвейера операций, который может охватывать несколько машин с разными возможностями.
- По моему опыту (почти 30 лет распределенных, многопроцессных и многопоточных приложений) распределение работы - не самая сложная часть. Сбор результатов и координация независимых процессов - это то место, где возникает большинство ошибок потоков (опять же, по моему опыту). Разобрав проблему до простой цепочки, интервьюер может увидеть, насколько хорошо кандидат думает о координации. Кроме того, интервьюер имеет возможность задавать всевозможные дополнительные вопросы, например: «Хорошо, что, если каждый поток должен отправить свое слово в другой поток для реконструкции».
- Думает ли кандидат о том, как модель памяти процессора может повлиять на реализацию? Если результаты одной операции никогда не сбрасываются из кэша L1, это ошибка, даже если нет явного параллелизма.
- Отделяет ли кандидат многопоточность от логики приложения?
Последний пункт, на мой взгляд, самый важный. Опять же, исходя из моего опыта, становится значительно сложнее отлаживать многопоточный код, если многопоточность смешана с логикой приложения (просто посмотрите на все вопросы о Swing в SO для примеров). Я считаю, что лучший многопоточный код написан как автономный однопоточный код с четко определенными передачами.
Имея это в виду, мой подход должен дать каждому потоку две очереди: одну для ввода, одну для вывода. Поток блокируется при чтении входной очереди, забирает первое слово из строки и передает остаток строки в свою выходную очередь. Некоторые из особенностей этого подхода:
- Код приложения отвечает за чтение очереди, выполнение действий с данными и запись в очередь. Не имеет значения, является ли он многопоточным или нет, или очередь является очередью в памяти на одном компьютере или очередью на основе TCP между машинами, которые живут в противоположных сторонах мира.
- Поскольку код приложения написан как если бы он был однопоточным, он может быть детерминированно протестирован без необходимости использования большого количества скаффолдингов.
- На этапе выполнения код приложения владеет обрабатываемой строкой. Не нужно заботиться о синхронизации с одновременно выполняющимися потоками.
Тем не менее, есть еще много серых областей, которые компетентный интервьюер может исследовать:
- «Хорошо, но мы смотрим, что вы знаете о примитивах параллелизма. Можете ли вы создать очередь блокировки?» Ваш первый ответ, конечно, должен заключаться в том, что вы будете использовать предварительно построенную очередь блокировки с выбранной вами платформы. Однако, если вы понимаете потоки, вы можете создать реализацию очереди длиной до дюжины строк кода, используя любые примитивы синхронизации, которые поддерживает ваша платформа.
- «Что делать, если один шаг в этом процессе занимает очень много времени?» Вам следует подумать о том, хотите ли вы ограниченную или неограниченную очередь вывода, как вы можете обрабатывать ошибки и как повлиять на общую пропускную способность, если у вас есть задержка.
- Как эффективно поставить в очередь исходную строку. Не обязательно проблема, если вы имеете дело с очередями в памяти, но может быть проблемой, если вы перемещаетесь между машинами. Вы также можете изучить оболочки, доступные только для чтения, поверх основного неизменяемого байтового массива.
Наконец, если у вас есть опыт параллельного программирования, вы можете поговорить о некоторых фреймворках (например, Akka для Java / Scala), которые уже следуют этой модели.