@synchronized
@synchronized(object) 指令使用的 object 为该锁的唯一标识,只有当标识相同时,才满足互斥,所以如果线程 2 中的 @synchronized(self) 改为 @synchronized(self.view),则线程 2 就不会被阻塞,@synchronized 指令实现锁的优点就是我们不需要在代码中显式的创建锁对象,便可以实现锁的机制,但作为一种预防措施,@synchronized 块会隐式的添加一个异常处理例程来保护代码,该处理例程会在异常抛出的时候自动释放互斥锁。@synchronized 还有一个好处就是不用担心忘记解锁了。如果在 @synchronized(object) {} 内部 object 被释放或被设为 nil,从测试结果来看,不会产生问题,但如果 object 一开始就是 nil,则失去了加锁的功能。不过虽然 nil 不行,但是 [NSNull null] 是可以的。
-
objc4-750
版本之前(iOS 12
之前)@synchronized
是一个基于pthread_mutex_t
封装的递归锁,之后实现则发生了改变,底层的封装变为了os_unfair_lock
。下面验证它,在@synchronized
打断点,并且打开Debug-> Debug Workflow -> Always Show Disassembly
:
#pragma mark - Private Methods
- (void)recuresiveAction {
// ➡️ 在下面 @synchronized 上打断点
@synchronized ([self class]) {
NSLog(@"🌰🌰🌰 count = %d", count);
if (count > 0) {
count--;
[self recuresiveAction];
}
}
}
// 汇编 objc_Simple`-[ViewController recuresiveAction]:
...
0x10868fc4b <+43>: callq 0x108690360 ; symbol stub for: objc_sync_enter // 👈 看到调用了 objc_sync_enter 函数
...
0x10868fcc7 <+167>: callq 0x108690366 ; symbol stub for: objc_sync_exit // 👈 看到调用了 objc_sync_exit 函数
...
复制代码
看到 @synchronized
调用了 objc_sync_enter
和 objc_sync_exit
函数,下面从 objc4-781
中看一下这两个函数的实现,objc_sync_exit
和 objc_sync_enter
函数都位于 objc-sync.mm
。
作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS交流群:834688868,不管你是大牛还是小白都欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长!
- 文末有惊喜,请一定看到最后!
// End synchronizing on 'obj'.
// Returns OBJC_SYNC_SUCCESS or OBJC_SYNC_NOT_OWNING_THREAD_ERROR
int objc_sync_exit(id obj)
{
int result = OBJC_SYNC_SUCCESS;
if (obj) {
SyncData* data = id2data(obj, RELEASE);
if (!data) {
result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
} else {
bool okay = data->mutex.tryUnlock(); // 尝试解锁,返回 true 表示解锁成功,否则表示失败
if (!okay) {
result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
}
}
} else {
// @synchronized(nil) does nothing
}
return result;
}
复制代码
// Begin synchronizing on 'obj'.
// Allocates recursive mutex associated with 'obj' if needed.
// Returns OBJC_SYNC_SUCCESS once lock is acquired.
int objc_sync_enter(id obj)
{
int result = OBJC_SYNC_SUCCESS;
if (obj) {
// 根据传入的对象,来获取一个锁,所以使用 @synchronized 时传入对象很重要
SyncData* data = id2data(obj, ACQUIRE);
ASSERT(data);
data->mutex.lock(); // 这里使用 data 的 mutex 成员变量执行 lock
} else {
// @synchronized(nil) does nothing
// 传入 nil 则什么也不做
if (DebugNilSync) {
_objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug");
}
objc_sync_nil();
}
return result;
}
复制代码
SyncData
定义:
typedef struct alignas(CacheLineSize) SyncData {
struct SyncData* nextData;
DisguisedPtr<objc_object> object;
int32_t threadCount; // number of THREADS using this block
recursive_mutex_t mutex;
} SyncData;
复制代码
recursive_mutex_t
是使用 using
关键字声明的模版类:using recursive_mutex_t = recursive_mutex_tt<LOCKDEBUG>;
下面看一下 recursive_mutex_tt
底层结构:
template <bool Debug>
class recursive_mutex_tt : nocopy_t {
// 底层封装的是 os_unfair_recursive_lock
os_unfair_recursive_lock mLock;
public:
constexpr recursive_mutex_tt() : mLock(OS_UNFAIR_RECURSIVE_LOCK_INIT) {
lockdebug_remember_recursive_mutex(this);
}
constexpr recursive_mutex_tt(const fork_unsafe_lock_t unsafe)
: mLock(OS_UNFAIR_RECURSIVE_LOCK_INIT)
{ }
void lock()
{
lockdebug_recursive_mutex_lock(this);
os_unfair_recursive_lock_lock(&mLock);
}
...
};
复制代码
objc4-723
中 recursive_mutex_tt
定义:
// 在 objc4-723 版本中 recursive_mutex_tt 的底层结构为
class recursive_mutex_tt : nocopy_t {
// 底层封装的是互斥锁 pthread_mutex_t
pthread_mutex_t mLock;
public:
recursive_mutex_tt() : mLock(PTHREAD_RECURSIVE_MUTEX_INITIALIZER) {
lockdebug_remember_recursive_mutex(this);
}
recursive_mutex_tt(const fork_unsafe_lock_t unsafe)
: mLock(PTHREAD_RECURSIVE_MUTEX_INITIALIZER)
{ }
...
}
复制代码
继续查看 os_unfair_recursive_lock
底层实现:
/*!
* @typedef os_unfair_recursive_lock
*
* @abstract
* Low-level lock that allows waiters to block efficiently on contention.
*
* @discussion
* See os_unfair_lock.
*
*/
OS_UNFAIR_RECURSIVE_LOCK_AVAILABILITY
typedef struct os_unfair_recursive_lock_s {
os_unfair_lock ourl_lock; // 底层为互斥锁 os_unfair_lock
uint32_t ourl_count; // 因为 @synchronized 为递归锁,所以需要记录加锁次数
} os_unfair_recursive_lock, *os_unfair_recursive_lock_t;
复制代码
到这里可以确认了底层是 os_unfair_lock
。 然后我们还注意到 OS_UNFAIR_RECURSIVE_LOCK_AVAILABILITY
:
/*! @group os_unfair_recursive_lock SPI
*
* @abstract
* Similar to os_unfair_lock, but recursive.
* 与 os_unfair_lock 相似,但是是递归的。
*
* @discussion
* Must be initialized with OS_UNFAIR_RECURSIVE_LOCK_INIT
* 必须使用 OS_UNFAIR_RECURSIVE_LOCK_INIT 进行初始化
*/
#define OS_UNFAIR_RECURSIVE_LOCK_AVAILABILITY \
__OSX_AVAILABLE(10.14) __IOS_AVAILABLE(12.0) \
__TVOS_AVAILABLE(12.0) __WATCHOS_AVAILABLE(5.0)
复制代码
这里表明是 iOS 12.0
之后才是出现的。至此可验证 iOS 12.0
后 @synchronized
是一个封装了 os_unfair_lock
的递归锁(os_unfair_recursive_lock
)。
-
@synchronized(obj){...}
传入一个对象obj
进行加锁,如果传入空,则不执行操作。
@synchronized 使用
#import "ViewController.h"
static int count = 3;
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
dispatch_queue_t global_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
__weak typeof(self) _self = self;
dispatch_async(global_queue, ^{
__strong typeof(_self) self = _self;
if (!self) return;
[self recuresiveAction];
});
}
#pragma mark - Private Methods
- (void)recuresiveAction {
@synchronized ([self class]) {
NSLog(@"🌰🌰🌰 count = %d", count);
if (count > 0) {
count--;
[self recuresiveAction];
}
}
}
#pragma mark - dealloc
- (void)dealloc {
NSLog(@"🧑🎤🧑🎤🧑🎤 dealloc 同时释放🔒...");
}
@end
// 打印结果:
🌰🌰🌰 count = 3
🌰🌰🌰 count = 2
🌰🌰🌰 count = 1
🌰🌰🌰 count = 0
🧑🎤🧑🎤🧑🎤 dealloc 同时释放🔒...
复制代码
dispatch_semaphore
dispatch_semaphore 是 GCD 用来同步的一种方式,与他相关的只有三个函数,一个是创建信号量,一个是等待信号量,一个是发送信号。
dispatch_semaphore 和 NSCondition 类似,都是一种基于信号的同步方式,但 NSCondition 信号只能发送,不能保存(如果没有线程在等待,则发送的信号会失效)。而 dispatch_semaphore 能保存发送的信号。dispatch_semaphore 的核心是 dispatch_semaphore_t 类型的信号量。
emsp;dispatch_semaphore_create(1) 方法可以创建一个 dispatch_semaphore_t 类型的信号量,设定信号量的初始化值为 1。注意,这里的传入参数必须大于等于 0,否则 dispatch_semaphore 会返回 NULL。 dispatch_semaphore_wait(signal, overTime) 方法会判断 signal 的信号值是否大于 0,大于 0 不会阻塞线程,消耗掉一个信号,执行后续任务。如果信号值为 0,该线程会和 NSCondition 一样直接进入 waiting 状态,等待其他线程发送信号唤醒线程去执行后续任务,或者当 overTime 时限到了,也会执行后续任务。 dispatch_semaphore_signal(signal) 发送信号,如果没有等待的线程调用信号,则使 signal 信号值加 1(做到对信号的保存)。一个 dispatch_semaphore_wait(signal, overTime)方法会去对应一个 dispatch_semaphore_signal(signal) 看起来像 NSLock 的 lock 和 unlock,其实可以这样理解,区别只在于有信号量这个参数,lock unlock 只能同一时间,一个线程访问被保护的临界区,而如果 dispatcch_semaphore 的信号量初始值为 x,则可以有 x 个线程同时访问被保护的临界区。
- 本来是用于控制线程的最大并发数量,我们将并发数量设置为
1
也可以认为是加锁的功能。 - 可能会用到的方法:
- 初始化
dispatch_semaphore_create()
传入的值为最大并发数量,设置为1
则达到加锁效果。 - 判断信号量的值
dispatch_semaphore_wait()
如果大于0
,则可以继续往下执行(同时信号量的值减去1
),如果信号量的值为0
,则线程进入休眠状态等待(此方法的第二个参数就是设置要等多久,一般是使用永久DISPATCH_TIME_FOREVER
)。 - 释放信号量
dispatch_semaphore_signal()
同时使信号量的值加上1
。
dispatch_semaphore 使用
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, assign) NSInteger sum;
@property (nonatomic, strong) dispatch_semaphore_t semaphore;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.sum = 0;
self.semaphore = dispatch_semaphore_create(1);
dispatch_queue_t global_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
__weak typeof(self) _self = self;
dispatch_async(global_queue, ^{
__strong typeof(_self) self = _self;
if (!self) return;
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
for (unsigned int i = 0; i < 10000; ++i) {
self.sum++;
}
dispatch_semaphore_signal(self.semaphore);
NSLog(@"🍐🍐🍐 %ld", (long)self.sum);
});
dispatch_async(global_queue, ^{
__strong typeof(_self) self = _self;
if (!self) return;
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
for (unsigned int i = 0; i < 10000; ++i) {
self.sum++;
}
dispatch_semaphore_signal(self.semaphore);
NSLog(@"🍎🍎🍎 %ld", (long)self.sum);
});
}
#pragma mark - dealloc
- (void)dealloc {
NSLog(@"🧑🎤🧑🎤🧑🎤 dealloc 同时释放🔒...");
}
@end
// 打印结果:
🍐🍐🍐 10000
🍎🍎🍎 20000
🧑🎤🧑🎤🧑🎤 dealloc 同时释放🔒...
复制代码
pthread_rwlock_t
学习 pthread_rwlock_t
读写锁之前,首先引入一个问题:“如何实现一个多读单写的模型?”,需求如下:
- 同时可以有多个线程读取。
- 同时只能有一个线程写入。
- 同时只能执行读取或者写入的一种。
首先想到的就是我们的 pthread_rwlock_t
。
-
读取加锁可以同时多个线程进行,写入同时只能一个线程进行,等待的线程处于休眠状态。
-
可能会用到的方法:
-
pthread_rwlock_init()
初始化一个读写锁 -
pthread_rwlock_rdlock()
读写锁的读取加锁 -
pthread_rwlock_wrlock()
读写锁的写入加锁 -
pthread_rwlock_unlock()
解锁 -
pthread_rwlock_destroy()
销毁锁
pthread_rwlock_t 使用
代码示例,测试代码主要看,打印读取可以同时出现几个,打印写入同时只会出现一个。
#import "ViewController.h"
#import <pthread.h>
@interface ViewController ()
@property (nonatomic, assign) pthread_rwlock_t lock;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[self rwlockType];
}
#pragma mark - Private methods
- (void)rwlockType {
pthread_rwlock_init(&self->_lock, NULL);
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
__weak typeof(self) _self = self;
for (unsigned int i = 0; i < 100; ++i) {
// 同时创建多个线程进行写入操作
dispatch_async(globalQueue, ^{
__weak typeof(_self) self = _self;
if (!self) return;
[self lockWriteAction];
});
dispatch_async(globalQueue, ^{
__weak typeof(_self) self = _self;
if (!self) return;
[self lockWriteAction];
});
dispatch_async(globalQueue, ^{
__weak typeof(_self) self = _self;
if (!self) return;
[self lockWriteAction];
});
// 同时创建多个线程进行读操作
dispatch_async(globalQueue, ^{
__strong typeof(_self) self = _self;
if (!self) return;
[self lockReadAction];
});
dispatch_async(globalQueue, ^{
__strong typeof(_self) self = _self;
if (!self) return;
[self lockReadAction];
});
dispatch_async(globalQueue, ^{
__strong typeof(_self) self = _self;
if (!self) return;
[self lockReadAction];
});
}
}
- (void)lockReadAction {
pthread_rwlock_rdlock(&self->_lock);
sleep(1);
NSLog(@"RWLock read action %@", [NSThread currentThread]);
pthread_rwlock_unlock(&self->_lock);
}
- (void)lockWriteAction {
pthread_rwlock_wrlock(&self->_lock);
sleep(1);
NSLog(@"RWLock Write Action %@", [NSThread currentThread]);
pthread_rwlock_unlock(&self->_lock);
}
#pragma mark - dealloc
-(void)dealloc {
NSLog(@"🚚🚚🚚 deallocing...");
pthread_rwlock_destroy(&self->_lock);
}
@end
// 打印结果: 可看到每次 write 操作同一个时间只执行一次,每次执行 write 操作至少相差 1 的时间,而 read 操作,几乎三次读取完全同一时刻进行
2020-08-23 21:56:47.918292+0800 algorithm_OC[17138:583665] RWLock Write Action <NSThread: 0x600001d45440>{number = 6, name = (null)}
2020-08-23 21:56:48.918953+0800 algorithm_OC[17138:583666] RWLock Write Action <NSThread: 0x600001d58740>{number = 4, name = (null)}
2020-08-23 21:56:49.924037+0800 algorithm_OC[17138:583667] RWLock Write Action <NSThread: 0x600001d06440>{number = 3, name = (null)}
2020-08-23 21:56:50.927716+0800 algorithm_OC[17138:583697] RWLock read action <NSThread: 0x600001d00d40>{number = 10, name = (null)}
2020-08-23 21:56:50.927716+0800 algorithm_OC[17138:583696] RWLock read action <NSThread: 0x600001d864c0>{number = 8, name = (null)}
2020-08-23 21:56:50.927721+0800 algorithm_OC[17138:583698] RWLock read action <NSThread: 0x600001da4b40>{number = 9, name = (null)}
...
复制代码
dispatch_barrier_async 实现多读单写
- 传入的并发队列必须是手动创建的,
dispatch_queue_create()
方式,如果传入串行队列或者通过dispatch_get_global_queue()
方式创建,则dispatch_barrier_async
的作用就跟dispatch_async
变的一样。 - 可能会用到的方法:
-
dispatch_queue_create()
创建并发队列 -
dispatch_barrier_async()
异步栅栏
dispatch_barrier_async 使用
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, strong) dispatch_queue_t queue;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[self barrierAsyncType];
}
#pragma mark - Private methods
- (void)barrierAsyncType {
self.queue = dispatch_queue_create("rw_queue", DISPATCH_QUEUE_CONCURRENT);
for (unsigned int i = 0; i < 100; ++i) {
// 同时创建多个线程进行写入操作
[self barrierWriteAction];
[self barrierWriteAction];
[self barrierWriteAction];
// 同时创建多个线程进行读取操作
[self barrierReadAction];
[self barrierReadAction];
[self barrierReadAction];
}
}
- (void)barrierReadAction {
dispatch_async(self.queue, ^{
sleep(1);
NSLog(@"barrier Read Action %@", [NSThread currentThread]);
});
}
- (void)barrierWriteAction {
// 写操作使用 dispatch_barrier_async
dispatch_barrier_async(self.queue, ^{
sleep(1);
NSLog(@"barrier Write Action %@", [NSThread currentThread]);
});
}
@end
// 打印结果: 从打印时间可以看出,write 操作是依序进行的,每次间隔 1 秒,而 read 操作几乎都是同时进行 3 次
2020-08-23 22:25:14.144265+0800 algorithm_OC[17695:604062] barrier Write Action <NSThread: 0x6000012a0180>{number = 5, name = (null)}
2020-08-23 22:25:15.148017+0800 algorithm_OC[17695:604062] barrier Write Action <NSThread: 0x6000012a0180>{number = 5, name = (null)}
2020-08-23 22:25:16.151869+0800 algorithm_OC[17695:604062] barrier Write Action <NSThread: 0x6000012a0180>{number = 5, name = (null)}
2020-08-23 22:25:17.156004+0800 algorithm_OC[17695:604062] barrier Read Action <NSThread: 0x6000012a0180>{number = 5, name = (null)}
2020-08-23 22:25:17.156040+0800 algorithm_OC[17695:604063] barrier Read Action <NSThread: 0x600001230340>{number = 6, name = (null)}
2020-08-23 22:25:17.156023+0800 algorithm_OC[17695:604065] barrier Read Action <NSThread: 0x6000012e6300>{number = 3, name = (null)}
...
复制代码
总结
锁粗略的效率排序(不同的锁可能更擅长不同的场景)
-
os_unfair_lock
(iOS 10
之后) -
OSSpinLock
(iOS 10
之前) -
dispatch_semaphore
(iOS
版本兼容性好) -
pthread_mutex_t
(iOS
版本兼容性好) -
NSLock
(基于pthread_mutex_t
封装) -
NSCondition
(基于pthread_mutex_t
封装) -
pthread_mutex_t(recursive)
递归锁的优先推荐 -
NSRecursiveLock
(基于pthread_mutex_t
封装) -
NSConditionLock
(基于NSCondition
封装) @synchronized
-
iOS 12
之前基于pthread_mutex_t
封装 -
iOS 12
之后基于os_unfair_lock
封装(iOS 12 之后它的效率应该不是最低,应该在 3/4 左右)
自旋锁和互斥锁的取舍 自旋锁和互斥锁怎么选择,其实这个问题已经没有什么意义,因为自旋锁 OSSpinLock
在 iOS 10
之后已经废弃了,而它的替换方案 os_unfair_lock
是互斥锁,但是我们仍然做一下对比: 自旋锁:
- 预计线程需要等待的时间较短
- 多核处理器
-
CPU
的资源不紧张
互斥锁:
- 预计线程需要等待的时间较长
- 单核处理器
- 临界区(加锁解锁之间的部分)有 I/O 操作
其它: 加锁和解锁的实现一定要配对出现,不然就会出现阻塞死锁的现象。
参考链接
参考链接:🔗
- 自旋锁
- 不再安全的 OSSpinLock
- iOS 锁 部分一
- 如何深入理解 iOS 开发中的锁?
- iOS 常见知识点(三):Lock
- iOS锁-OSSpinLock与os_unfair_lock
- os_unfair_lock pthread_mutex
- iOS 锁 部分一
- iOS 锁 部分二
- iOS 锁 部分三
- iOS中保证线程安全的几种方式与性能对比
- 关于 @synchronized,这儿比你想知道的还要多
作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS交流群:834688868,不管你是大牛还是小白都欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长!
网友评论