线程安全地访问 class 中的变量
Thread safe access to a variable in a class
在一个可能有多个线程运行的应用程序中,不确定这些方法是否会在多线程环境下被访问的可能性,但为了安全起见,我做了一个测试class演示一种情况。
一个方法 has
被编程为线程安全的(如果正确也请评论)但其他方法不是。
在这种情况下,remove
和add
中只有一行代码,是否有必要使它们成为线程安全的,还是会被夸大。
import Foundation
class Some {}
class Test {
var dict = [String: Some]()
func has(key: String) -> Bool {
var has = false
dispatch_sync(dispatch_queue_create("has", nil), { [unowned self] in
has = self.dict[key] != nil
})
return has
}
func remove(key: String) -> Some {
var ob = dict[key]
dict[key] = nil
return ob
}
func add(key: String, ob: Some) {
dict[key] = ob
}
}
评论后编辑
class Some {}
class Test {
var dict = [String: Some]()
private let queue: dispatch_queue_t = dispatch_queue_create("has", DISPATCH_QUEUE_CONCURRENT)
func has(key: String) -> Bool {
var has = false
dispatch_sync(queue) {
has = self.dict[key] != nil
}
return has
}
func remove(key: String) -> Some? { //returns
var removed: Some?
dispatch_barrier_sync(queue) {
removed = self.dict.removeValueForKey(key)
}
return removed
}
func add(key: String, ob: Some) { //not async
dispatch_barrier_sync(queue) {
self.dict[key] = ob
}
}
}
您检查密钥是否存在的方式不正确。您每次都在创建一个新队列,这意味着操作不会同步发生。
我的做法是这样的:
class Some {}
class Test {
var dict = [String: Some]()
private let queue: dispatch_queue_t = dispatch_queue_create("has", DISPATCH_QUEUE_CONCURRENT)
func has(key: String) -> Bool {
var has = false
dispatch_sync(queue) { [weak self] in
guard let strongSelf = self else { return }
has = strongSelf.dict[key] != nil
}
return has
}
func remove(key: String) {
dispatch_barrier_async(queue) { [weak self] in
guard let strongSelf = self else { return }
strongSelf.dict[key] = nil
}
}
func add(key: String, ob: Some) {
dispatch_barrier_async(queue) { [weak self] in
guard let strongSelf = self else { return }
strongSelf.dict[key] = ob
}
}
}
首先,我正在创建一个串行队列,它将用于访问字典作为对象的 属性,而不是每次都创建一个新队列。该队列是私有的,因为它仅在内部使用。
当我想从 class 中获取一个值时,我只是将一个块同步分派到队列并等待块完成,然后再 return 检查队列是否存在。由于这不会改变字典,因此对于并发队列中的多个此类块 运行 是安全的。
当我想在字典中添加或删除值时,我将块添加到队列中但带有障碍。这样做的目的是在 运行ning 时停止队列中的所有其他块。完成后,所有其他块可以同时 运行。我正在使用异步调度,因为我不需要等待 return 值。
假设您有多个线程试图查看键值是否存在或添加或删除值。如果您有很多读取,那么它们会同时发生,但是当其中一个块 运行 将更改字典时,所有其他块将等待此更改完成,然后再次开始 运行ning 。
这样既有取值时运行并发的速度和方便,也有字典变化时阻塞的线程安全
编辑添加
self
在块中被标记为 weak
,这样它就不会创建引用循环。正如评论中提到的@MartinR;有可能对象在块仍在队列中时被释放,如果发生这种情况,则 self 未定义,并且您可能会在尝试访问字典时遇到 运行 时间错误,因为它也可能被释放.
通过在块内声明self
设置为weak,如果对象存在,则self
不会为nil,并且可以有条件地解包成指向strongSelf
self
并且还创建了一个强引用,因此 self
在执行块中的指令时不会被释放。当这些指令完成时,strongSelf
将超出范围并释放对 self 的强引用。
这有时被称为 "strong self, weak self dance"。
再次编辑:Swift 3 版本
class Some {}
class Test {
var dict = [String: Some]()
private let queue = DispatchQueue(label: "has", qos: .default, attributes: .concurrent)
func has(key: String) -> Bool {
var has = false
queue.sync { [weak self] in
guard let strongSelf = self else { return }
has = strongSelf.dict[key] != nil
}
return has
}
func remove(key: String) {
queue.async(flags: .barrier) { [weak self] in
guard let strongSelf = self else { return }
strongSelf.dict[key] = nil
}
}
func add(key: String, ob: Some) {
queue.async(flags: .barrier) { [weak self] in
guard let strongSelf = self else { return }
strongSelf.dict[key] = ob
}
}
}
这是另一个 swift3 解决方案,它提供对 AnyObject 的线程安全访问。
如果需要,它会分配与 'object' 关联的递归 pthread_mutex。
class LatencyManager
{
private var latencies = [String : TimeInterval]()
func set(hostName: String, latency: TimeInterval) {
synchronizedBlock(lockedObject: latencies as AnyObject) { [weak self] in
self?.latencies[hostName] = latency
}
}
/// Provides thread-safe access to given object
private func synchronizedBlock(lockedObject: AnyObject, block: () -> Void) {
objc_sync_enter(lockedObject)
block()
objc_sync_exit(lockedObject)
}
}
然后你可以调用例如set(hostName: "whosebug.com", latency: 1)
更新
您可以简单地在 swift 文件中定义一个方法(而不是在 class 中):
/// Provides thread-safe access to given object
public func synchronizedAccess(to object: AnyObject, _ block: () -> Void)
{
objc_sync_enter(object)
block()
objc_sync_exit(object)
}
并像这样使用它:
synchronizedAccess(to: myObject) {
myObject.foo()
}
在一个可能有多个线程运行的应用程序中,不确定这些方法是否会在多线程环境下被访问的可能性,但为了安全起见,我做了一个测试class演示一种情况。
一个方法 has
被编程为线程安全的(如果正确也请评论)但其他方法不是。
在这种情况下,remove
和add
中只有一行代码,是否有必要使它们成为线程安全的,还是会被夸大。
import Foundation
class Some {}
class Test {
var dict = [String: Some]()
func has(key: String) -> Bool {
var has = false
dispatch_sync(dispatch_queue_create("has", nil), { [unowned self] in
has = self.dict[key] != nil
})
return has
}
func remove(key: String) -> Some {
var ob = dict[key]
dict[key] = nil
return ob
}
func add(key: String, ob: Some) {
dict[key] = ob
}
}
评论后编辑
class Some {}
class Test {
var dict = [String: Some]()
private let queue: dispatch_queue_t = dispatch_queue_create("has", DISPATCH_QUEUE_CONCURRENT)
func has(key: String) -> Bool {
var has = false
dispatch_sync(queue) {
has = self.dict[key] != nil
}
return has
}
func remove(key: String) -> Some? { //returns
var removed: Some?
dispatch_barrier_sync(queue) {
removed = self.dict.removeValueForKey(key)
}
return removed
}
func add(key: String, ob: Some) { //not async
dispatch_barrier_sync(queue) {
self.dict[key] = ob
}
}
}
您检查密钥是否存在的方式不正确。您每次都在创建一个新队列,这意味着操作不会同步发生。
我的做法是这样的:
class Some {}
class Test {
var dict = [String: Some]()
private let queue: dispatch_queue_t = dispatch_queue_create("has", DISPATCH_QUEUE_CONCURRENT)
func has(key: String) -> Bool {
var has = false
dispatch_sync(queue) { [weak self] in
guard let strongSelf = self else { return }
has = strongSelf.dict[key] != nil
}
return has
}
func remove(key: String) {
dispatch_barrier_async(queue) { [weak self] in
guard let strongSelf = self else { return }
strongSelf.dict[key] = nil
}
}
func add(key: String, ob: Some) {
dispatch_barrier_async(queue) { [weak self] in
guard let strongSelf = self else { return }
strongSelf.dict[key] = ob
}
}
}
首先,我正在创建一个串行队列,它将用于访问字典作为对象的 属性,而不是每次都创建一个新队列。该队列是私有的,因为它仅在内部使用。
当我想从 class 中获取一个值时,我只是将一个块同步分派到队列并等待块完成,然后再 return 检查队列是否存在。由于这不会改变字典,因此对于并发队列中的多个此类块 运行 是安全的。
当我想在字典中添加或删除值时,我将块添加到队列中但带有障碍。这样做的目的是在 运行ning 时停止队列中的所有其他块。完成后,所有其他块可以同时 运行。我正在使用异步调度,因为我不需要等待 return 值。
假设您有多个线程试图查看键值是否存在或添加或删除值。如果您有很多读取,那么它们会同时发生,但是当其中一个块 运行 将更改字典时,所有其他块将等待此更改完成,然后再次开始 运行ning 。
这样既有取值时运行并发的速度和方便,也有字典变化时阻塞的线程安全
编辑添加
self
在块中被标记为 weak
,这样它就不会创建引用循环。正如评论中提到的@MartinR;有可能对象在块仍在队列中时被释放,如果发生这种情况,则 self 未定义,并且您可能会在尝试访问字典时遇到 运行 时间错误,因为它也可能被释放.
通过在块内声明self
设置为weak,如果对象存在,则self
不会为nil,并且可以有条件地解包成指向strongSelf
self
并且还创建了一个强引用,因此 self
在执行块中的指令时不会被释放。当这些指令完成时,strongSelf
将超出范围并释放对 self 的强引用。
这有时被称为 "strong self, weak self dance"。
再次编辑:Swift 3 版本
class Some {}
class Test {
var dict = [String: Some]()
private let queue = DispatchQueue(label: "has", qos: .default, attributes: .concurrent)
func has(key: String) -> Bool {
var has = false
queue.sync { [weak self] in
guard let strongSelf = self else { return }
has = strongSelf.dict[key] != nil
}
return has
}
func remove(key: String) {
queue.async(flags: .barrier) { [weak self] in
guard let strongSelf = self else { return }
strongSelf.dict[key] = nil
}
}
func add(key: String, ob: Some) {
queue.async(flags: .barrier) { [weak self] in
guard let strongSelf = self else { return }
strongSelf.dict[key] = ob
}
}
}
这是另一个 swift3 解决方案,它提供对 AnyObject 的线程安全访问。
如果需要,它会分配与 'object' 关联的递归 pthread_mutex。
class LatencyManager
{
private var latencies = [String : TimeInterval]()
func set(hostName: String, latency: TimeInterval) {
synchronizedBlock(lockedObject: latencies as AnyObject) { [weak self] in
self?.latencies[hostName] = latency
}
}
/// Provides thread-safe access to given object
private func synchronizedBlock(lockedObject: AnyObject, block: () -> Void) {
objc_sync_enter(lockedObject)
block()
objc_sync_exit(lockedObject)
}
}
然后你可以调用例如set(hostName: "whosebug.com", latency: 1)
更新
您可以简单地在 swift 文件中定义一个方法(而不是在 class 中):
/// Provides thread-safe access to given object
public func synchronizedAccess(to object: AnyObject, _ block: () -> Void)
{
objc_sync_enter(object)
block()
objc_sync_exit(object)
}
并像这样使用它:
synchronizedAccess(to: myObject) {
myObject.foo()
}