Pesquisei o livro Swift, mas não consigo encontrar a versão Swift de @synchronized. Como faço para exclusão mútua no Swift?
removeFirst()
?
Pesquisei o livro Swift, mas não consigo encontrar a versão Swift de @synchronized. Como faço para exclusão mútua no Swift?
removeFirst()
?
Respostas:
Você pode usar o GCD. É um pouco mais detalhado do que @synchronized
, mas funciona como um substituto:
let serialQueue = DispatchQueue(label: "com.test.mySerialQueue")
serialQueue.sync {
// code
}
Eu mesmo estava procurando isso e cheguei à conclusão de que ainda não há nenhuma construção nativa dentro do swift.
Eu criei essa pequena função auxiliar com base em alguns dos códigos que eu vi de Matt Bridges e outros.
func synced(_ lock: Any, closure: () -> ()) {
objc_sync_enter(lock)
closure()
objc_sync_exit(lock)
}
O uso é bastante direto
synced(self) {
println("This is a synchronized closure")
}
Há um problema que encontrei com isso. Passar em uma matriz como o argumento de bloqueio parece causar um erro muito obtuso do compilador neste momento. Caso contrário, embora pareça funcionar como desejado.
Bitcast requires both operands to be pointer or neither
%26 = bitcast i64 %25 to %objc_object*, !dbg !378
LLVM ERROR: Broken function found, compilation aborted!
@synchronized
bloco, mas observe que ele não é idêntico a uma instrução de bloco embutida real como o @synchronized
bloco no Objective-C, porque as instruções return
e break
não funcionam mais para pular da função / loop circundante como seria se fosse uma afirmação comum.
defer
palavra-chave para garantir que objc_sync_exit
seja chamado mesmo que seja closure
lançado.
Gosto e uso muitas das respostas aqui, portanto, escolho o que for melhor para você. Dito isso, o método que eu prefiro quando preciso de algo como o objetivo-c @synchronized
usa a defer
instrução introduzida no swift 2.
{
objc_sync_enter(lock)
defer { objc_sync_exit(lock) }
//
// code of critical section goes here
//
} // <-- lock released when this block is exited
A coisa agradável sobre este método, é que a sua seção crítica pode sair do bloco contendo de qualquer forma desejada (por exemplo, return
, break
, continue
, throw
), e "as declarações dentro a declaração de adiamento são executados não importa como o controle do programa é transferido." 1
lock
? Como é lock
inicializado?
lock
é qualquer objeto objetivo-c.
Você pode colocar declarações entre objc_sync_enter(obj: AnyObject?)
e objc_sync_exit(obj: AnyObject?)
. A palavra-chave @synchronized está usando esses métodos nos bastidores. ie
objc_sync_enter(self)
... synchronized code ...
objc_sync_exit(self)
objc_sync_enter
e objc_sync_exit
os métodos são definidos no Objc-sync.h e são de código aberto: opensource.apple.com/source/objc4/objc4-371.2/runtime/…
objc_sync_enter(…)
e objc_sync_exit(…)
são cabeçalhos públicos fornecidos pelo iOS / macOS / etc. APIs (parece que elas estão dentro ….sdk
do caminho usr/include/objc/objc-sync.h
) . A maneira mais fácil de descobrir se algo é uma API pública ou não é (no Xcode) digitar o nome da função (por exemplo objc_sync_enter()
, os argumentos não precisam ser especificados para as funções C) e , em seguida, clique com o botão direito do mouse. Se ele mostrar o arquivo de cabeçalho para essa API, você será bom (já que não conseguiria ver o cabeçalho se não fosse público) .
O análogo da @synchronized
diretiva do Objective-C pode ter um tipo de retorno arbitrário e um bom rethrows
comportamento no Swift.
// Swift 3
func synchronized<T>(_ lock: AnyObject, _ body: () throws -> T) rethrows -> T {
objc_sync_enter(lock)
defer { objc_sync_exit(lock) }
return try body()
}
O uso da defer
instrução permite retornar diretamente um valor sem introduzir uma variável temporária.
No Swift 2, adicione o @noescape
atributo ao fechamento para permitir mais otimizações:
// Swift 2
func synchronized<T>(lock: AnyObject, @noescape _ body: () throws -> T) rethrows -> T {
objc_sync_enter(lock)
defer { objc_sync_exit(lock) }
return try body()
}
Com base nas respostas de GNewc [1] (onde eu gosto do tipo de retorno arbitrário) e Tod Cunningham [2] (onde eu gosto defer
).
SWIFT 4
No Swift 4, você pode usar as filas de despacho GCDs para bloquear recursos.
class MyObject {
private var internalState: Int = 0
private let internalQueue: DispatchQueue = DispatchQueue(label:"LockingQueue") // Serial by default
var state: Int {
get {
return internalQueue.sync { internalState }
}
set (newState) {
internalQueue.sync { internalState = newState }
}
}
}
.serial
parece estar indisponível. Mas .concurrent
está disponível. : /
myObject.state = myObject.state + 1
simultaneamente, não contaria o total de operações, mas produziria um valor não determinístico. Para resolver esse problema, o código de chamada deve ser agrupado em uma fila serial para que a leitura e a gravação ocorram atomicamente. É claro que os Obj-c @synchronised
têm o mesmo problema, portanto, nesse sentido, sua implementação está correta.
myObject.state += 1
é uma combinação de uma operação de leitura e gravação. Outro segmento ainda pode estar entre eles para definir / escrever um valor. Conforme objc.io/blog/2018/12/18/atomic-variables , seria mais fácil executar o set
bloqueio / fechamento em sincronização e não a variável em si.
Usando a resposta de Bryan McLemore, eu a estendi para apoiar objetos que oferecem uma mansão segura com a capacidade de adiamento do Swift 2.0.
func synchronized( lock:AnyObject, block:() throws -> Void ) rethrows
{
objc_sync_enter(lock)
defer {
objc_sync_exit(lock)
}
try block()
}
rethrows
para simplificar o uso com fechamentos sem arremesso (sem necessidade de usar try
), conforme mostrado na minha resposta .
Para adicionar funcionalidade de retorno, você pode fazer o seguinte:
func synchronize<T>(lockObj: AnyObject!, closure: ()->T) -> T
{
objc_sync_enter(lockObj)
var retVal: T = closure()
objc_sync_exit(lockObj)
return retVal
}
Posteriormente, você pode chamá-lo usando:
func importantMethod(...) -> Bool {
return synchronize(self) {
if(feelLikeReturningTrue) { return true }
// do other things
if(feelLikeReturningTrueNow) { return true }
// more things
return whatIFeelLike ? true : false
}
}
Swift 3
Este código tem a capacidade de reentrada e pode funcionar com chamadas de função assíncronas. Nesse código, depois que someAsyncFunc () é chamado, outro fechamento de função na fila serial será processado, mas será bloqueado pelo semáforo.wait () até o sinal () ser chamado. internalQueue.sync não deve ser usado, pois ele bloqueará o thread principal se não me engano.
let internalQueue = DispatchQueue(label: "serialQueue")
let semaphore = DispatchSemaphore(value: 1)
internalQueue.async {
self.semaphore.wait()
// Critical section
someAsyncFunc() {
// Do some work here
self.semaphore.signal()
}
}
objc_sync_enter / objc_sync_exit não é uma boa ideia sem tratamento de erros.
Na sessão "Compreendendo falhas e logs de falhas" 414 da WWDC 2018, eles mostram a seguinte maneira, usando DispatchQueues com sincronização.
No swift 4 deve ser algo como o seguinte:
class ImageCache {
private let queue = DispatchQueue(label: "sync queue")
private var storage: [String: UIImage] = [:]
public subscript(key: String) -> UIImage? {
get {
return queue.sync {
return storage[key]
}
}
set {
queue.sync {
storage[key] = newValue
}
}
}
}
De qualquer forma, você também pode fazer leituras mais rapidamente usando filas simultâneas com barreiras. As leituras de sincronização e assíncrona são executadas simultaneamente e a gravação de um novo valor aguarda o término das operações anteriores.
class ImageCache {
private let queue = DispatchQueue(label: "with barriers", attributes: .concurrent)
private var storage: [String: UIImage] = [:]
func get(_ key: String) -> UIImage? {
return queue.sync { [weak self] in
guard let self = self else { return nil }
return self.storage[key]
}
}
func set(_ image: UIImage, for key: String) {
queue.async(flags: .barrier) { [weak self] in
guard let self = self else { return }
self.storage[key] = image
}
}
}
Use o NSLock no Swift4:
let lock = NSLock()
lock.lock()
if isRunning == true {
print("Service IS running ==> please wait")
return
} else {
print("Service not running")
}
isRunning = true
lock.unlock()
Aviso A classe NSLock usa threads POSIX para implementar seu comportamento de bloqueio. Ao enviar uma mensagem de desbloqueio para um objeto NSLock, você deve ter certeza de que a mensagem é enviada do mesmo encadeamento que enviou a mensagem de bloqueio inicial. Desbloquear um bloqueio de um thread diferente pode resultar em um comportamento indefinido.
No Swift 5 moderno, com capacidade de retorno:
/**
Makes sure no other thread reenters the closure before the one running has not returned
*/
@discardableResult
public func synchronized<T>(_ lock: AnyObject, closure:() -> T) -> T {
objc_sync_enter(lock)
defer { objc_sync_exit(lock) }
return closure()
}
Use-o assim, para aproveitar o recurso de valor de retorno:
let returnedValue = synchronized(self) {
// Your code here
return yourCode()
}
Ou assim:
synchronized(self) {
// Your code here
yourCode()
}
GCD
). Parece que essencialmente ninguém usa ou entende como usar Thread
. Estou muito feliz com isso - ao passo que GCD
está cheio de truques e limitações.
Experimente: NSRecursiveLock
Um bloqueio que pode ser adquirido várias vezes pelo mesmo encadeamento sem causar um conflito.
let lock = NSRecursiveLock()
func f() {
lock.lock()
//Your Code
lock.unlock()
}
func f2() {
lock.lock()
defer {
lock.unlock()
}
//Your Code
}
Figura Vou postar minha implementação do Swift 5, construída com base nas respostas anteriores. Obrigado rapazes! Achei útil ter um que retorne um valor também, então eu tenho dois métodos.
Aqui está uma classe simples para fazer primeiro:
import Foundation
class Sync {
public class func synced(_ lock: Any, closure: () -> ()) {
objc_sync_enter(lock)
defer { objc_sync_exit(lock) }
closure()
}
public class func syncedReturn(_ lock: Any, closure: () -> (Any?)) -> Any? {
objc_sync_enter(lock)
defer { objc_sync_exit(lock) }
return closure()
}
}
Em seguida, use-o assim se precisar de um valor de retorno:
return Sync.syncedReturn(self, closure: {
// some code here
return "hello world"
})
Ou:
Sync.synced(self, closure: {
// do some work synchronously
})
public class func synced<T>(_ lock: Any, closure: () -> T)
, funciona para ambos, nulo e qualquer outro tipo. Há também o material regrows.
xCode 8.3.1, swift 3.1
Leia o valor de gravação de diferentes segmentos (assíncrono).
class AsyncObject<T>:CustomStringConvertible {
private var _value: T
public private(set) var dispatchQueueName: String
let dispatchQueue: DispatchQueue
init (value: T, dispatchQueueName: String) {
_value = value
self.dispatchQueueName = dispatchQueueName
dispatchQueue = DispatchQueue(label: dispatchQueueName)
}
func setValue(with closure: @escaping (_ currentValue: T)->(T) ) {
dispatchQueue.sync { [weak self] in
if let _self = self {
_self._value = closure(_self._value)
}
}
}
func getValue(with closure: @escaping (_ currentValue: T)->() ) {
dispatchQueue.sync { [weak self] in
if let _self = self {
closure(_self._value)
}
}
}
var value: T {
get {
return dispatchQueue.sync { _value }
}
set (newValue) {
dispatchQueue.sync { _value = newValue }
}
}
var description: String {
return "\(_value)"
}
}
print("Single read/write action")
// Use it when when you need to make single action
let obj = AsyncObject<Int>(value: 0, dispatchQueueName: "Dispatch0")
obj.value = 100
let x = obj.value
print(x)
print("Write action in block")
// Use it when when you need to make many action
obj.setValue{ (current) -> (Int) in
let newValue = current*2
print("previous: \(current), new: \(newValue)")
return newValue
}
extensão DispatchGroup
extension DispatchGroup {
class func loop(repeatNumber: Int, action: @escaping (_ index: Int)->(), completion: @escaping ()->()) {
let group = DispatchGroup()
for index in 0...repeatNumber {
group.enter()
DispatchQueue.global(qos: .utility).async {
action(index)
group.leave()
}
}
group.notify(queue: DispatchQueue.global(qos: .userInitiated)) {
completion()
}
}
}
classe ViewController
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//sample1()
sample2()
}
func sample1() {
print("=================================================\nsample with variable")
let obj = AsyncObject<Int>(value: 0, dispatchQueueName: "Dispatch1")
DispatchGroup.loop(repeatNumber: 5, action: { index in
obj.value = index
}) {
print("\(obj.value)")
}
}
func sample2() {
print("\n=================================================\nsample with array")
let arr = AsyncObject<[Int]>(value: [], dispatchQueueName: "Dispatch2")
DispatchGroup.loop(repeatNumber: 15, action: { index in
arr.setValue{ (current) -> ([Int]) in
var array = current
array.append(index*index)
print("index: \(index), value \(array[array.count-1])")
return array
}
}) {
print("\(arr.value)")
}
}
}
Com os wrappers de propriedades do Swift, é isso que estou usando agora:
@propertyWrapper public struct NCCSerialized<Wrapped> {
private let queue = DispatchQueue(label: "com.nuclearcyborg.NCCSerialized_\(UUID().uuidString)")
private var _wrappedValue: Wrapped
public var wrappedValue: Wrapped {
get { queue.sync { _wrappedValue } }
set { queue.sync { _wrappedValue = newValue } }
}
public init(wrappedValue: Wrapped) {
self._wrappedValue = wrappedValue
}
}
Então você pode simplesmente fazer:
@NCCSerialized var foo: Int = 10
ou
@NCCSerialized var myData: [SomeStruct] = []
Em seguida, acesse a variável como faria normalmente.
DispatchQueue
que está oculto para o usuário. Encontrei esta referência SO para tranqüilizar: stackoverflow.com/a/35022486/1060314
Em conclusão, aqui é apresentada uma maneira mais comum que inclui valor de retorno ou nulo e
import Foundation
extension NSObject {
func synchronized<T>(lockObj: AnyObject!, closure: () throws -> T) rethrows -> T
{
objc_sync_enter(lockObj)
defer {
objc_sync_exit(lockObj)
}
return try closure()
}
}
Por que dificultar e aborrecer os bloqueios? Use Barreiras de Despacho.
Uma barreira de despacho cria um ponto de sincronização dentro de uma fila simultânea.
Enquanto estiver em execução, nenhum outro bloco na fila poderá executar, mesmo que seja concorrente e outros núcleos estejam disponíveis.
Se isso soa como um bloqueio exclusivo (gravação), é. Blocos sem barreira podem ser considerados bloqueios compartilhados (lidos).
Desde que todo o acesso ao recurso seja realizado através da fila, as barreiras fornecem uma sincronização muito barata.
Com base no obeuroburɳ , teste um caso de subclasse
class Foo: NSObject {
func test() {
print("1")
objc_sync_enter(self)
defer {
objc_sync_exit(self)
print("3")
}
print("2")
}
}
class Foo2: Foo {
override func test() {
super.test()
print("11")
objc_sync_enter(self)
defer {
print("33")
objc_sync_exit(self)
}
print("22")
}
}
let test = Foo2()
test.test()
1
2
3
11
22
33
Outro método é criar uma superclasse e depois herdá-la. Dessa forma, você pode usar o GCD mais diretamente
class Lockable {
let lockableQ:dispatch_queue_t
init() {
lockableQ = dispatch_queue_create("com.blah.blah.\(self.dynamicType)", DISPATCH_QUEUE_SERIAL)
}
func lock(closure: () -> ()) {
dispatch_sync(lockableQ, closure)
}
}
class Foo: Lockable {
func boo() {
lock {
....... do something
}
}