Tôi đã tìm kiếm sách Swift, nhưng không thể tìm thấy phiên bản Swift của @synyncized. Làm cách nào để loại trừ lẫn nhau trong Swift?
removeFirst()
?
Tôi đã tìm kiếm sách Swift, nhưng không thể tìm thấy phiên bản Swift của @synyncized. Làm cách nào để loại trừ lẫn nhau trong Swift?
removeFirst()
?
Câu trả lời:
Bạn có thể sử dụng GCD. Nó dài dòng hơn một chút @synchronized
, nhưng hoạt động như một sự thay thế:
let serialQueue = DispatchQueue(label: "com.test.mySerialQueue")
serialQueue.sync {
// code
}
Tôi đã tự mình tìm kiếm điều này và đi đến kết luận rằng không có cấu trúc bản địa nào bên trong nhanh chóng cho việc này.
Tôi đã tạo nên chức năng trợ giúp nhỏ này dựa trên một số mã tôi đã thấy từ Matt Bridges và những người khác.
func synced(_ lock: Any, closure: () -> ()) {
objc_sync_enter(lock)
closure()
objc_sync_exit(lock)
}
Cách sử dụng khá dễ dàng
synced(self) {
println("This is a synchronized closure")
}
Có một vấn đề tôi đã tìm thấy với điều này. Chuyển vào một mảng làm đối số khóa dường như gây ra lỗi trình biên dịch rất khó hiểu tại thời điểm này. Nếu không, mặc dù nó dường như làm việc như mong muốn.
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
khối một cách độc đáo, nhưng lưu ý rằng nó không giống với câu lệnh khối dựng sẵn thực sự như @synchronized
khối trong Objective-C, bởi vì return
và các break
câu lệnh không còn hoạt động để nhảy ra khỏi hàm / vòng lặp xung quanh như nó sẽ là nếu đây là một tuyên bố bình thường.
defer
từ khóa mới để đảm bảo objc_sync_exit
được gọi ngay cả khi closure
ném.
Tôi thích và sử dụng nhiều câu trả lời ở đây, vì vậy tôi sẽ chọn cách nào phù hợp nhất với bạn. Điều đó nói rằng, phương pháp tôi thích khi tôi cần một cái gì đó như object-c @synchronized
sử dụng defer
câu lệnh được giới thiệu trong swift 2.
{
objc_sync_enter(lock)
defer { objc_sync_exit(lock) }
//
// code of critical section goes here
//
} // <-- lock released when this block is exited
Những điều tốt đẹp về phương pháp này, là phần quan trọng của bạn có thể thoát khỏi khối chứa trong bất kỳ thời trang mong muốn (ví dụ return
, break
, continue
, throw
), và "những điều khoản trong tuyên bố Hoãn được thực hiện không có vấn đề kiểm soát chương trình được chuyển giao như thế nào." 1
lock
gì Làm thế nào được lock
khởi tạo?
lock
là bất kỳ đối tượng mục tiêu-c.
Bạn có thể tuyên bố sandwich giữa objc_sync_enter(obj: AnyObject?)
và objc_sync_exit(obj: AnyObject?)
. Từ khóa @synyncized đang sử dụng các phương thức đó trong trang bìa. I E
objc_sync_enter(self)
... synchronized code ...
objc_sync_exit(self)
objc_sync_enter
và objc_sync_exit
là các phương thức được định nghĩa trong Objc-sync.h và là nguồn mở: opensource.apple.com/source/objc4/objc4-371.2/r
objc_sync_enter(…)
và objc_sync_exit(…)
là các tiêu đề công khai được cung cấp bởi iOS / macOS / vv. API (có vẻ như chúng nằm trong ….sdk
đường dẫn usr/include/objc/objc-sync.h
) . Cách dễ nhất để tìm hiểu xem có thứ gì đó là API công khai hay không là (trong Xcode) nhập tên hàm (ví dụ: các objc_sync_enter()
đối số không cần được chỉ định cho các hàm C) , sau đó thử nhấp vào lệnh. Nếu nó hiển thị cho bạn tệp tiêu đề cho API đó, thì bạn vẫn ổn (vì bạn sẽ không thể xem tiêu đề nếu nó không công khai) .
Tương tự của lệnh @synchronized
từ Objective-C có thể có kiểu trả về tùy ý và rethrows
hành vi đẹp trong 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()
}
Việc sử dụng defer
câu lệnh cho phép trả về trực tiếp một giá trị mà không đưa ra một biến tạm thời.
Trong Swift 2, thêm @noescape
thuộc tính vào bao đóng để cho phép tối ưu hóa hơn:
// 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()
}
Dựa trên các câu trả lời từ GNewc [1] (nơi tôi thích kiểu trả về tùy ý) và Tod Castyham [2] (nơi tôi thích defer
).
Chuyển 4
Trong Swift 4, bạn có thể sử dụng GCD gửi hàng đợi để khóa tài nguyên.
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
dường như là không có Nhưng .concurrent
có sẵn. : /
myObject.state = myObject.state + 1
đồng thời, nó sẽ không tính tổng số thao tác mà thay vào đó mang lại giá trị không xác định. Để giải quyết vấn đề đó, mã cuộc gọi nên được bọc trong một hàng đợi nối tiếp để cả việc đọc và ghi xảy ra nguyên tử. Tất nhiên Obj-c @synchronised
có cùng một vấn đề, vì vậy theo nghĩa đó, việc thực hiện của bạn là chính xác.
myObject.state += 1
là sự kết hợp của đọc và sau đó là thao tác ghi. Một số luồng khác vẫn có thể đi vào giữa để đặt / ghi giá trị. Theo objc.io/blog/2018/12/18/atomic-variables , việc chạy set
khối / đóng đồng bộ thay vào đó sẽ không dễ dàng hơn và không phải dưới biến.
Sử dụng câu trả lời của Bryan McLemore, tôi đã mở rộng nó để hỗ trợ các đối tượng ném vào một trang viên an toàn với khả năng trì hoãn Swift 2.0.
func synchronized( lock:AnyObject, block:() throws -> Void ) rethrows
{
objc_sync_enter(lock)
defer {
objc_sync_exit(lock)
}
try block()
}
rethrows
để đơn giản hóa việc sử dụng với các lần đóng không ném (không cần sử dụng try
), như thể hiện trong câu trả lời của tôi .
Để thêm chức năng trả về, bạn có thể làm điều này:
func synchronize<T>(lockObj: AnyObject!, closure: ()->T) -> T
{
objc_sync_enter(lockObj)
var retVal: T = closure()
objc_sync_exit(lockObj)
return retVal
}
Sau đó, bạn có thể gọi nó bằng cách sử dụng:
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
Mã này có khả năng nhập lại và có thể hoạt động với các lệnh gọi hàm không đồng bộ. Trong mã này, sau khi một sốAsyncFunc () được gọi, một hàm đóng khác trên hàng đợi nối tiếp sẽ xử lý nhưng bị chặn bởi semaphore.wait () cho đến khi tín hiệu () được gọi. InternalQueue.sync không nên được sử dụng vì nó sẽ chặn luồng chính nếu tôi không nhầm.
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 không phải là một ý tưởng tốt mà không xử lý lỗi.
Trong phiên "Tìm hiểu sự cố và Nhật ký sự cố" phiên 414 của WWDC 2018, họ trình bày cách sau bằng cách sử dụng DispatchQueues với đồng bộ hóa.
Trong swift 4 nên giống như sau:
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
}
}
}
}
Dù sao, bạn cũng có thể đọc nhanh hơn bằng cách sử dụng hàng đợi đồng thời với các rào cản. Việc đọc đồng bộ hóa và không đồng bộ được thực hiện đồng thời và viết một giá trị mới chờ các hoạt động trước đó kết thúc.
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
}
}
}
Sử dụng NSLock trong 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()
Cảnh báo Lớp NSLock sử dụng các luồng POSIX để thực hiện hành vi khóa của nó. Khi gửi tin nhắn mở khóa đến một đối tượng NSLock, bạn phải chắc chắn rằng tin nhắn đó được gửi từ cùng một chủ đề đã gửi tin nhắn khóa ban đầu. Mở khóa từ một luồng khác nhau có thể dẫn đến hành vi không xác định.
Trong Swift 5 hiện đại, với khả năng quay trở lại:
/**
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()
}
Sử dụng nó như thế này, để tận dụng khả năng giá trị trả về:
let returnedValue = synchronized(self) {
// Your code here
return yourCode()
}
Hoặc như thế khác:
synchronized(self) {
// Your code here
yourCode()
}
GCD
). Có vẻ như về cơ bản không ai sử dụng hoặc hiểu cách sử dụng Thread
. Tôi hài lòng với nó - trong khi GCD
đầy những vấn đề và giới hạn.
Hãy thử: NSRecursiveLock
Một khóa có thể được mua lại nhiều lần bởi cùng một chủ đề mà không gây ra bế tắc.
let lock = NSRecursiveLock()
func f() {
lock.lock()
//Your Code
lock.unlock()
}
func f2() {
lock.lock()
defer {
lock.unlock()
}
//Your Code
}
Hình tôi sẽ đăng triển khai Swift 5 của mình, dựa trên các câu trả lời trước. Cảm ơn các bạn! Tôi thấy thật hữu ích khi có một cái cũng trả về một giá trị, vì vậy tôi có hai phương thức.
Đây là một lớp đơn giản để thực hiện đầu tiên:
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()
}
}
Sau đó sử dụng nó như vậy nếu cần một giá trị trả về:
return Sync.syncedReturn(self, closure: {
// some code here
return "hello world"
})
Hoặc là:
Sync.synced(self, closure: {
// do some work synchronously
})
public class func synced<T>(_ lock: Any, closure: () -> T)
, làm việc cho cả hai, void và bất kỳ loại nào khác. Ngoài ra còn có các công cụ thoái lui.
xCode 8.3.1, nhanh chóng 3.1
Đọc giá trị ghi từ các chủ đề khác nhau (không đồng bộ).
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
}
mở rộng công văn
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()
}
}
}
lớp ViewContoder
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)")
}
}
}
Với trình bao bọc tài sản của Swift, đây là những gì tôi đang sử dụng:
@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
}
}
Sau đó, bạn có thể làm:
@NCCSerialized var foo: Int = 10
hoặc là
@NCCSerialized var myData: [SomeStruct] = []
Sau đó truy cập vào biến như bạn thường làm.
DispatchQueue
thứ bị ẩn khỏi người dùng. Tôi tìm thấy tài liệu tham khảo SO này để giúp tôi thoải mái: stackoverflow.com/a/35022486/1060314
Tóm lại, Ở đây đưa ra cách phổ biến hơn bao gồm giá trị trả về hoặc khoảng trống và ném
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()
}
}
Tại sao làm cho nó khó khăn và rắc rối với ổ khóa? Sử dụng hàng rào công văn.
Một rào cản công văn tạo ra một điểm đồng bộ hóa trong một hàng đợi đồng thời.
Trong khi nó đang chạy, không có khối nào khác trên hàng đợi được phép chạy, ngay cả khi nó đồng thời và các lõi khác có sẵn.
Nếu đó là âm thanh như một khóa độc quyền (ghi), nó là. Các khối không rào cản có thể được coi là khóa chia sẻ (đọc).
Miễn là tất cả quyền truy cập vào tài nguyên được thực hiện thông qua hàng đợi, các rào cản cung cấp đồng bộ hóa rất rẻ.
Dựa trên ɲeuroburɳ , kiểm tra trường hợp của lớp con
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
Một phương pháp khác là tạo ra một siêu lớp và sau đó kế thừa nó. Bằng cách này bạn có thể sử dụng GCD trực tiếp hơn
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
}
}