Что произойдёт, если два потока одновременно вызывают next() у одного итератора Sequence?
Это не гарантирует безопасный параллельный обход: IteratorProtocol не требует потокобезопасности. Два потока могут одновременно изменить внутреннее состояние итератора, что приводит к гонке данных, пропуску или повторной выдаче элементов и неопределённому результату. Доступ к одному итератору нужно сериализовать либо использовать независимые итераторы с отдельно проверенной безопасностью источника.
Итератор в Swift предназначен для последовательного получения элементов через изменяемое состояние обхода. Такая абстракция позволяет работать с ленивыми, одноразовыми и потенциально вычисляемыми на лету последовательностями, но сама по себе не является механизмом синхронизации.
Разделение интерфейса обхода и хранения данных делает Sequence универсальным, однако контракт намеренно не обещает безопасный одновременный доступ из нескольких потоков. Потокобезопасность должна обеспечиваться конкретным типом и кодом, который организует доступ к нему.
Вызов next() обычно изменяет позицию итератора: он запоминает, какой элемент выдавать следующим. Если два потока читают и изменяют это состояние одновременно, операции могут пересекаться.
Даже если исходная коллекция является значимым типом, общий итератор представляет собой общее изменяемое состояние. Без синхронизации нельзя полагаться ни на количество полученных элементов, ни на их порядок, ни на отсутствие повторов.
next() имеет изменяющий семантику вызов: он либо возвращает следующий элемент, либо возвращает nil, когда обход завершён. Протокол IteratorProtocol не содержит требований о блокировках, атомарности или поддержке многопоточного доступа.
Нужно защищать каждый вызов next() общей синхронизацией, например очередью, мьютексом или актором. Важно сериализовать именно всю операцию получения элемента, а не только последующую обработку: иначе два вызова всё равно могут одновременно продвинуть одно состояние.
Создание двух итераторов может устранить гонку между самими итераторами, но не даёт универсальной гарантии для источника. Для пользовательских или ссылочных последовательностей отдельно проверяют, разрешает ли источник одновременное чтение и не изменяется ли он во время обхода.
Также нельзя автоматически считать копирование итератора способом распараллеливания. Итератор может быть структурой, содержащей ссылку на общее состояние, поэтому копии иногда продолжают управлять одним внутренним ресурсом.
Сервис распределяет элементы последовательности между двумя рабочими потоками и передаёт им один общий итератор. Вариант с прямыми одновременными вызовами next() прост, но небезопасен: возможны гонки и непредсказуемое распределение элементов.
Вариант с блокировкой вокруг next() корректен, но блокировка становится узким местом и фактически превращает получение элементов в последовательную операцию. Вариант с независимыми диапазонами или заранее разделёнными коллекциями лучше масштабируется, если источник поддерживает безопасный доступ по позициям.
Практический выбор — не делить один итератор между потоками, а заранее разделить вход на независимые части. Если это невозможно, доступ к итератору сериализуют и затем выполняют тяжёлую обработку элементов уже вне критической секции.
Достаточно ли сделать итератор Sendable, чтобы разрешить параллельные вызовы?
Нет. Sendable описывает возможность безопасно передавать значение между изолированными контекстами Swift Concurrency, но не превращает изменяемый объект в потокобезопасный. Тип должен корректно обеспечивать синхронизацию своего состояния; объявление @unchecked Sendable лишь переносит ответственность на разработчика.
Безопасно ли параллельно обходить одну неизменяемую коллекцию через разные итераторы?
Для стандартных значимых коллекций при отсутствии мутаций это обычно соответствует их модели использования, но общий универсальный контракт Sequence такой гарантии не формулирует. Пользовательская последовательность может обращаться к общему ссылочному ресурсу или выполнять побочные эффекты. Поэтому безопасность определяется конкретной реализацией источника, а не только фактом создания разных итераторов.
Можно ли синхронизировать только обработку возвращённого элемента?
Нет. Обработка элемента после next() не защищает внутреннее состояние итератора от одновременного изменения. Синхронизация должна охватывать сам вызов next() и получение его результата; длительную независимую обработку следует выполнять после выхода из критической секции.