Мое приложение использует класс A
и его подклассы SubA_x
.
A
имеет статическое свойство serialNumber
, которое изменяется классом A
только при инициализации подкласса.
Он устанавливает свойство let name
, чтобы каждый подкласс имел уникальное имя.
Вот код:
class A {
static var serialNumber = 0
let name: String
init( /* some parameters */ ) {
A.serialNumber += 1
self.name = "\(A.serialNumber)"
}
}
final class SubA_1: A {
init( /* some parameters */ ) {
super.init( /* some parameters */ )
}
}
При строгой проверке параллелизма в Swift 6 строка, в которой инициализируется serialNumber
, выдает ошибку.
Статическое свойство «serialNumber» не является безопасным для параллелизма, поскольку оно неизолированное глобальное общее изменяемое состояние
Я понимаю, что каждый подкласс A
может модифицировать var serialNumber
из любого потока, поэтому возможны гонки данных.
Но как мне реализовать эту функциональность безопасным для параллелизма способом?
Я попытался предоставить serialNumber
актера:
actor SerialNumberManager {
private var serialNumber = 0
func getNextSerialNumber() -> Int {
serialNumber += 1
return serialNumber
}
}
но тогда я не могу вызвать getNextSerialNumber()
в init
, кроме случаев, когда init
является асинхронным.
Но тогда я смогу инициализировать подкласс только в асинхронном контексте и т. д.
Вероятно, я мог бы обеспечить собственную синхронизацию на основе GCD, но должен быть способ сделать это в рамках параллелизма Swift.
@Подметальная машина Ты прав. Я рефакторинг существующего кода и наивно думал, что смогу обойтись минимальными изменениями. Однако теперь я знаю, что это невозможно с использованием непотокобезопасного кода.
Если вам нужно потокобезопасное общее состояние, логичным подходом будет использование actor
. Однако если вам нужен такой, который можно вызывать из синхронных контекстов, вы можете просто написать свой собственный менеджер, реализуя собственную ручную синхронизацию (либо с помощью последовательной очереди GCD, либо, как показано ниже, с блокировкой):
class SerialNumberManager: @unchecked Sendable {
static let shared = SerialNumberManager()
private let lock = NSLock()
private var serialNumber = 0
private init() { }
func nextSerialNumber() -> Int {
lock.withLock {
serialNumber += 1
return serialNumber
}
}
}
Обратите внимание: мы будем использовать @unchecked Sendable
только тогда, когда реализуем ручную синхронизацию, как указано выше.
И вы можете использовать его так:
class A {
let serialNumber = SerialNumberManager.shared.nextSerialNumber()
let name: String
init( /* some parameters */ ) {
self.name = "\(serialNumber)"
}
}
В качестве альтернативы вы можете использовать OSAllocatedUnfairLock, который уже есть Sendable
:
import os.lock
class A {
private static let serialNumber = OSAllocatedUnfairLock(initialState: 0)
let name: String
init( /* some parameters */ ) {
let value = Self.serialNumber.withLock { value in
value += 1
return value
}
self.name = "\(value)"
}
}
Для полноты картины другие альтернативы включают атомы или UUID.
«Но тогда я могу инициализировать подкласс только в асинхронном контексте», и почему это не так? Это цена, которую вы должны заплатить за безопасность.