Swift 与 os_unfair_lock_lock 的访问竞赛
Swift access race with os_unfair_lock_lock
我制作了一个自定义 属性 包装器,它提供了一种使用 os_unfair_lock
在互斥上下文中访问数据的方法。在启用 TSAN 测试我的包装器后,在使用 os_unfair_lock_lock
(如下图所示)
获取锁时报告了访问竞争错误
不知何故,TSAN 报告了一个本应是线程安全的锁定结构,但实际上并非如此。这是怎么回事?
根据 WWDC 2016 演讲“Swift 3 中的 GCD 并发编程”,演讲者在 18:07 左右指出
Traditionally you would use a lock. And in Swift, since you have the entire Darwin module at your disposition, you will actually see the struct based traditional C locks. However [emphasis added], Swift assumes that anything that's a struct can be moved, and that doesn't work with a mutex or with a lock.
解决方案是桥接到 Objective-C 并创建一个 class 将 os_unfair_lock
包装为 ivar:
And if you want something that's smaller and that looks like the locks that you have in C, then you have to call into Objective-C and introduce a base class in Objective-C that has your lock as an ivar
在这种情况下,类似于
UnfairLock.h
#ifndef UnfairLock_h
#define UnfairLock_h
@import Foundation;
@import os;
@interface UnfairLock : NSObject
-(void)unfairlyAcquire;
-(void)unlock;
@end
#endif /* UnfairLock_h */
UnfairLock.m
#import <Foundation/Foundation.h>
#import "UnfairLock.h"
@implementation UnfairLock {
os_unfair_lock _lock;
}
-(instancetype)init {
self = [super init];
if (self) {
_lock = OS_UNFAIR_LOCK_INIT;
}
return self;
}
-(void)unfairlyAcquire {
os_unfair_lock_lock(&_lock);
}
-(void)unlock {
os_unfair_lock_unlock(&_lock);
}
@end
您的 的另一种(可能更直接)方法是直接在 Swift 中堆分配锁,而不是桥接到 Objective-C 来执行此操作. Objective-C 方法通过从具有不同语义的不同语言调用锁定函数来避免此问题 — C 和 Objective-C 不 移动 或传递逻辑删除值类型通过 inout 引用进入函数;但是你也可以通过完全不使用 inout 引用来避免纯 Swift 中的问题:
let lock = UnsafeMutablePointer<os_unfair_lock>.allocate(capacity: 1)
lock.initialize(to: .init())
// later:
os_unfair_lock_lock(lock)
defer { os_unfair_lock_unlock(lock) }
堆分配允许您将指针直接传递给函数,指针在 Swift 中是 reference 类型 — 而 Swift 可以移动指针值本身,它引用的内存将保持不变(并且有效)。
如果你走这条路,当你想拆除锁时不要忘记取消初始化和释放内存:
lock.deinitialize(count: 1)
lock.deallocate()
如果您愿意,可以在 Swift 中创建类似的 UnfairLock
界面,包括您自己的功能 mutexExecute
:
typealias UnfairLock = UnsafeMutablePointer<os_unfair_lock>
extension UnfairLock {
static func createLock() -> UnfairLock {
let l = UnfairLock.allocate(capacity: 1)
l.initialize(to: .init())
return l
}
static func destructLock(_ lock: UnfairLock) {
lock.deinitialize(count: 1)
lock.deallocate()
}
func whileLocked<T>(_ action: () throws -> T) rethrows -> T {
os_unfair_lock_lock(self)
defer { os_unfair_lock_unlock(self) }
return try action()
}
}
用法:
init() {
lock = UnfairLock.createLock()
}
deinit {
UnfairLock.destructLock(lock)
}
func performThing() -> Foo {
return lock.whileLocked {
// some operation that returns a Foo
}
}
我制作了一个自定义 属性 包装器,它提供了一种使用 os_unfair_lock
在互斥上下文中访问数据的方法。在启用 TSAN 测试我的包装器后,在使用 os_unfair_lock_lock
(如下图所示)
不知何故,TSAN 报告了一个本应是线程安全的锁定结构,但实际上并非如此。这是怎么回事?
根据 WWDC 2016 演讲“Swift 3 中的 GCD 并发编程”,演讲者在 18:07 左右指出
Traditionally you would use a lock. And in Swift, since you have the entire Darwin module at your disposition, you will actually see the struct based traditional C locks. However [emphasis added], Swift assumes that anything that's a struct can be moved, and that doesn't work with a mutex or with a lock.
解决方案是桥接到 Objective-C 并创建一个 class 将 os_unfair_lock
包装为 ivar:
And if you want something that's smaller and that looks like the locks that you have in C, then you have to call into Objective-C and introduce a base class in Objective-C that has your lock as an ivar
在这种情况下,类似于
UnfairLock.h
#ifndef UnfairLock_h
#define UnfairLock_h
@import Foundation;
@import os;
@interface UnfairLock : NSObject
-(void)unfairlyAcquire;
-(void)unlock;
@end
#endif /* UnfairLock_h */
UnfairLock.m
#import <Foundation/Foundation.h>
#import "UnfairLock.h"
@implementation UnfairLock {
os_unfair_lock _lock;
}
-(instancetype)init {
self = [super init];
if (self) {
_lock = OS_UNFAIR_LOCK_INIT;
}
return self;
}
-(void)unfairlyAcquire {
os_unfair_lock_lock(&_lock);
}
-(void)unlock {
os_unfair_lock_unlock(&_lock);
}
@end
您的
let lock = UnsafeMutablePointer<os_unfair_lock>.allocate(capacity: 1)
lock.initialize(to: .init())
// later:
os_unfair_lock_lock(lock)
defer { os_unfair_lock_unlock(lock) }
堆分配允许您将指针直接传递给函数,指针在 Swift 中是 reference 类型 — 而 Swift 可以移动指针值本身,它引用的内存将保持不变(并且有效)。
如果你走这条路,当你想拆除锁时不要忘记取消初始化和释放内存:
lock.deinitialize(count: 1)
lock.deallocate()
如果您愿意,可以在 Swift 中创建类似的 UnfairLock
界面,包括您自己的功能 mutexExecute
:
typealias UnfairLock = UnsafeMutablePointer<os_unfair_lock>
extension UnfairLock {
static func createLock() -> UnfairLock {
let l = UnfairLock.allocate(capacity: 1)
l.initialize(to: .init())
return l
}
static func destructLock(_ lock: UnfairLock) {
lock.deinitialize(count: 1)
lock.deallocate()
}
func whileLocked<T>(_ action: () throws -> T) rethrows -> T {
os_unfair_lock_lock(self)
defer { os_unfair_lock_unlock(self) }
return try action()
}
}
用法:
init() {
lock = UnfairLock.createLock()
}
deinit {
UnfairLock.destructLock(lock)
}
func performThing() -> Foo {
return lock.whileLocked {
// some operation that returns a Foo
}
}