开发中经常遇见的@synchronized,平常都说在多线程中它不是安全的,今天就从底层探索一下它为什么不安全。先看个例子:
_testArr = [NSMutableArray array];
for (int i= 0; i< 1000; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self creatNewArray];
});
}
- (void)creatNewArray {
NSMutableArray *newArr =[NSMutableArray array];
@synchronized (_testArr) {//无法保证多线程安全;
_testArr = newArr;//崩溃
}
}
打开僵尸指针运行
崩溃原因可以理解为,比如有线程1
、线程2
、线程3
,线程1
和线程2
同时锁的是_testArr
,在线程1
括号里,_testArr
变成了newArr
,那么轮到线程2
时,_testArr
不是最初的数组了,而线程3
上锁的时候,锁的是newArr
,所以不是同一把锁,这样在多线程下就像没有锁一样。
- 接下来在运行时断点进入汇编查看,这便是
@synchronized
的调用:
- 那么我们就从objc4-750源码探究一下这两个方法:
int objc_sync_enter(id obj)
{
int result = OBJC_SYNC_SUCCESS;
if (obj) {
SyncData* data = id2data(obj, ACQUIRE);//对象被保存用来判断是否同一把锁
assert(data);
data->mutex.lock();//加递归锁;同一个对象才是同一把锁
} else {
// @synchronized(nil) does nothing
if (DebugNilSync) {
_objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug");
}
objc_sync_nil();
}
return result;
}
在内部其实加了递归锁,对象会被SyncData
保存作为锁的关联:
- 结束时就是解锁:
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();//解锁
if (!okay) {
result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
}
}
} else {
// @synchronized(nil) does nothing
}
return result;
}
- 解决
所以使用时一般锁不变的对象,比如可以锁self
:
- (void)creatNewArray {
NSMutableArray *newArr =[NSMutableArray array];
@synchronized (self) {
_testArr = newArr;
}
}
网友评论