一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第16天,点击查看活动详情。
多线程
多线程方案有如下几种
判断以下代码是否会产生线程死锁
// 🌰 1
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"打印");
});
// 🌰 2
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
dispatch_sync(queue, ^{
NSLog(@"打印");
});
});
结论为都会产生死锁
线程死锁总结:使用sync 往 当前串行队列添加任务,会产生死锁
判断以下代码输出结果
- (void)test {
NSLog(@"2");
}
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"1");
[self performSelector:@selector(test) withObject:nil afterDelay:.0];
NSLog(@"3");
});
// 1
// 3
因为performSelector:withObject:afterDelay:方法本质是添加了一个NSTimer定时器,并将定时器添加到当前runloop中,但是当前线程是没有保活的,也就是执行完NSLog(@"3"),线程就挂掉了,虽然afterDelay设置为.0,但也是个延迟操作,需要在下一个runloop生命周期才能生效,因此需要设置runloop的一个运行时长
- (void)test {
NSLog(@"2");
}
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"1");
[self performSelector:@selector(test) withObject:nil afterDelay:.0];
NSLog(@"3");
// 需要注意以下代码添加位置,因为runMode:beforeDate:是开启了一个一定时长的循环,也就是在这时间内都是循环在当前位置,runMode:beforeDate:方法后面的代码将不会执行到
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
});
多线程加锁
多线程一般需要考虑线程同步问题,以及防止死锁(一些递归调用,嵌套调用会导致死锁,还有同步主队列任务也会死锁)。还有某些依赖条件下,锁的问题,如增删操作,当删除操作的线程先加锁,但是此时无数据可删,此时需要条件等待(如pthread_cond_wait() 此操作会将当前线程休眠,放开锁,被唤醒后会再次加锁,与pthread_cond_signal() 配对使用,signal是激活一个等待该条件的线程)
-
OSSpinLock:
- 自旋锁,加锁的地方会忙等,一直占用CPU资源,直到锁放开。此方法存在优先级反转问题,即如果优先级低的线程先加锁,然后优先级高的线程会忙等,但是由于优先级高的线程分配的资源更多,会存在优先级的线程资源不足而无法继续执行,此时互相等待,就造成死锁状态。
-
os_unfair_lock
- 用于替代OSSpinLock,等待锁的线程处于休眠状态而不是忙等。
-
pthread_mutex
- 互斥锁,等待锁的线程休眠,可以设置递归锁,以及条件等待
-
NSLock
- 对pthread_mutex的封装
-
NSRecursiveLock
- 对pthread_mutex的封装,递归锁
-
NSCondition
- 对mutex和cond的封装
-
NSConditionLock
- 对NSCondition的进一步封装
-
dispatch_semaphore
- 控制线程并发访问的最大数量
iOS内存布局
Tagged Pointer
如何判断一个指针是否为Tagged Pointer?
iOS平台,最高有效位是1(第64bit)
Mac平台,最低有效位是1
// 判断下列两个方法是否能正常运行,结果是否一致
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (NSInteger i = 0; i < 1000; i++) {
self.name = [NSString stringWithFormat:@"abcdefghijklmn"];
}
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (NSInteger i = 0; i < 1000; i++) {
self.name = [NSString stringWithFormat:@"abc"];
}
});
// 结论
此处需要结合ARC下set方法的底层实现,以及Tagged Pointer技术
- (void)setName:(NSString *)name {
if (_name != name) {
[_name release];
_name = [name copy];
}
}
可以看到,在多线程情况下,如果同时调用[_name release];方法,则会导致过度释放问题,导致坏内存访问。
至于@“abc”字符串,因为其使用的是Tagged Pointer技术,也就是直接存储在地址中,赋值操作是直接地址赋值
dealloc实现
内部会调用rootDealloc(), 此方法内部判断当前对象是否是taggedPointer, 根据isa判断是否是nonpointer、weakly_referenced、has_assoc、has_cxx_dtor、has_sidetable_rc,如果是就走快速释放,否则调用object_dispose()方法,此方法内部调用objc_destructInstance()方法,然后调用C函数free()
objc_destructInstance()方法内部,清除成员变量,移除关联对象,调用当前对象的clearDeallocating()方法,将指向当前对象的弱指针置为nil,弱指针存在SideTables这个哈希表中
ARC
ARC是LLVM+Runtime相互协作的结果,利用LLVM编译器自动在代码合适的位置添加release或autorelease, 利用runtime在对象释放的时候将弱引用置空