У меня есть этот код:
actor Actor {
@MainActor
var checker: @MainActor @Sendable () -> Void = {}
init(checker: @escaping @Sendable () -> Void) {
self.checker = checker
_ = self.checker // access it in nonisolated context
}
nonisolated func noniso() {
Task { @MainActor in
_ = self.checker
}
}
}
Насколько я понимаю, инициализация неизолирована, потому что я могу создать актера в неизолированном контексте:
let actor = Actor {}
Однако в неизолированной функции noniso
мне нужно использовать @MainActor
, чтобы получить доступ к self.checker
. В противном случае произойдет ошибка компилятора.
Почему я могу получить доступ к этому изолированному ивару checker
в этом неизолированном контексте в init?
Пожалуйста, не судите, почему я поставил @MainActor
ivar в актер и т. д. Это всего лишь экспериментальный код, позволяющий мне изучить быстрый параллелизм.
обновил код, удалив код, вызывающий init
из noniso
, поскольку он не имеет значения
Ах, я понимаю, в чем ваше замешательство. Во-первых, неполное имя checker
относится к параметру, а не к свойству. Я предполагаю, что вы имеете в виду self.checker
. Доступ к этому безопасен в init
, , если self никуда не убежит . Нет ничего, что могло бы вызвать гонку.
В чем разница между функциями init
и noniso
в том, что они имеют различное поведение?
Обычную неизолированную функцию можно вызывать из двух потоков одновременно, и они будут соревноваться друг с другом. То же самое не относится к init
. Если оба потока вызывают init
, будет создано всего два отдельных экземпляра актера, и каждый из них будет иметь свое собственное свойство checker
. Единственный способ сделать доступ к изолированному свойству в init
небезопасным - это self
каким-то образом сбежать до того, как init
вернется (см. связанный вопрос).
о да, я это пропустил - действительно, я собирался использовать self.checker. обновил код. Часть побега мне немного сложнее понять — вы имеете в виду сохранение self
после init
возвращения?
Нет, «побег» в данном случае означает передачу self
другим потокам до того, как init
вернется. Если бы вы это сделали, доступ к изолированным свойствам в init
был бы небезопасен.
Вам это также может быть интересно: github.com/swiftlang/swift-evolution/blob/main/proposals/…
Неасинхронные инициализаторы в актере действительно не изолированы от актера из-за их синхронной природы - переход актера не может быть выполнен.
Однако компилятор может доказать, что доступ к self.checker
в инициализаторе безопасен. Если два потока вызовут init
одновременно, то будет создано всего два экземпляра актора, каждый со своим собственным checker
.
Во время выполнения инициализатора ни один другой поток не может получить доступ к self
(объекту, который инициализируется), если только инициализатор не передаст self
куда-то еще.
Поскольку checker
является изолированным главным действующим лицом, хорошим примером этого является попытка передать self
в функцию @MainActor
.
actor SomeActor {
@MainActor
var checker: @MainActor @Sendable () -> Void = {}
init(checker: @escaping @Sendable () -> Void) {
self.checker = checker
foo(self) // error!
self.checker = { /* something */ }
}
}
@MainActor
func foo(_ a: SomeActor) {
// now foo has access to 'self.checker' too!
a.checker = { /* something else */ }
}
Здесь компилятор правильно выдает вам ошибку. Если бы компилятор позволил это, self.checker = { /* something */ }
и a.checker = { /* something else */ }
потенциально могли бы работать в разных потоках, и бог знает, что в конце будет записано в checker
.
Другой пример — захват self
во время Task
, бегущего по главному актеру.
init(checker: @escaping @Sendable () -> Void) {
self.checker = checker
// comment out the below line and the error is gone
Task { @MainActor in self.checker = { /* something */ } }
print(self.checker) // error here
}
Точно так же небезопасно вызывать какие-либо изолированные методы для себя в init. Эти методы могут запускать новые Task
, которые будут соревноваться с тем, что делает init
. Пройдите по ссылке, чтобы узнать больше.
Дополнительную информацию о том, как Swift определяет, сбежал ли self
, см. в разделе «инициализаторы с неизолированным я» в SE-0327.
немного перефразировал