多线程
- GCD
- NSOperation(AFnetwork可以查看)
- NSThread
- 多线程与锁
GCD的相关问题
- 同步/异步和串行/并发
- dispatch_barrier_async
- dispatch_group
同步?异步?串行?并发
- dispatch_sync(serial_queue,^{});
- dispatch_async(serial_queue,^{});
- dispatch_sync(concurrent_queue,^{});
- dispatch_async(concurrent_queue,^{});
在主线程中开启一个GCD同步任务
- 队列引起的循环等待,造成的死锁
- 在主线程中开启了一个同步任务,而这个同步任务,需要你前面的任务完成,而主线程在iOS中是一个特殊的线程,它会一直存在于你的程序结束之前。
首先ViewDidLoad执行处理,在执行过程中,他需要调用block,当block同步调用完成之后,这个ViewDidload才能继续向下走,ViewDidload方法处理结束,需要block调用完成以后。block任务要想执行,主队列是一个先进先出的性质,所以它要等到ViewDidLoad执行完成以后才能调用block的任务。这就造成了循环等待。
简单来讲就是,同步任务就是要等待队列中的上个任务完成以后才能调用这个同步任务。而这个同步任务,在上个任务之中。这就造成互相等待。
在主线程中开启一个GCD异步任务
在ViewDidLoad当中,在主线程中开启一个GCD异步任务。ViewDidLoad这个任务跟GCD异步任务并没有依赖关系,所以不会出现死锁
多线程的面试真题
-(void)GCDTest1{
//全局并发队列
dispatch_queue_t q = dispatch_get_global_queue(0, 0);
//当前线程第一个打印日志1
NSLog(@"1");
//同步任务到全局队列中,所以dispatch_sync里面的代码会在主线程中执行。
dispatch_sync(q, ^{
//第二个打印日志2
NSLog(@"2");
//同步任务,会执行
dispatch_sync(q, ^{
//第三个打印日志3
NSLog(@"3");
});
//第四个 打印日志4
NSLog(@"4");
});
//第五个打印日志5
NSLog(@"5");
}
异步串行
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"经常用到的主线程操作");
});
异步并发
-(void)GCDTest3{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"1");
//这个方法不会运行,因为runloop在子线程中默认是不开启的,所以用这句话不会执行
[self performSelector:@selector(printlog) withObject:nil afterDelay:0];
NSLog(@"3");
});
}
打印结果是1、3。
如何运用dispatch_barrier_async(栅栏函数)
对一个磁盘DB进行读写,如何设计多读单写?
- 读者、读者并发
- 读者、写者互斥
- 写者、写者互斥
用一个dispatch_barrier_async栅栏放入到并发队列来处理写操作。
- (id)GG_objectForkey:(NSString *)key{
__block id obj;
//并发任务就是可以多个执行
dispatch_sync(concurrent_queue, ^{
obj = [self->userCenterDic objectForKey:key];
});
//同步操作能立刻返回结果
return obj;
}
-(void)GG_setObject:(id)obj forKey:(NSString *)key{
//异步栅栏调用设置数据
dispatch_barrier_sync(concurrent_queue, ^{
[self->userCenterDic setObject:obj forKey:key];
});
}
调度组dispatch_group_async
使用GCD实现这个需求:A、B、C是那个任务并发,完成后执行任务D?
- (void)handle{
dispatch_group_t group = dispatch_group_create();
for (NSURL *url in arrayURLs) {
dispatch_group_async(group, concurrent_queue, ^{
NSLog(@"url is %@",url);
});
}
dispatch_group_notify(group, concurrent_queue, ^{
//当调度组中的任务全部完成以后才会调用这个函数
});
}
NSOperation
需要和NSOperationQueue配合使用来实现多线程方案
优势:
- 添加移除任务依赖,这些特点NSthread不具备,GCD不好实现的
- (void)addDependency:(NSOperation *)op;
- (void)removeDependency:(NSOperation *)op;
- 任务执行状态控制
- 最大并发量
任务执行状态控制
- isReady 当前任务是否处于就绪状态
- isExecuting 当前任务是否是正在执行状态
- isFinished 当前任务是否完成
- isCancelled 当前任务是否已取消
状态控制
- 如果只重写了main方法,系统的底层实现会控制变更任务执行完成状态,以及任务退出
- 如果重写了start方法,自行控制任务状态,在合适的时机更新状态
- 重写了start方法就是相当于,将系统控制状态的代码覆盖了。
NSOperation的源码
系统是如何移除一个isFinished=YES的NSOperation的?
NSOperationQueue通过KVO来移除NSOperation的。
NSthread
启动流程
start -》创建pthread -〉 main -》 (RunLoop事件的运行循环)[target performselect:selector]->exit
如果需要写一个常驻的线程需要我们在Select方法里面实现常驻runloop。
锁
经常使用的锁
@synchchronized
atomic
OSSpinLock
NSRecursiveLock
NSLock
dispatch_semaphore_t
-
@synchchronized
- 在创建单例对象的时候使用,来保证在多线程环境下,创建的对象是唯一的。
-
atomic
- 修饰属性的关键字
- 对被修饰对象进行原子操作(不负责使用)
- 只负责赋值,不负责对象使用
-
OSSpinLock
- 自旋锁,循环等待询问,不是放当前资源
- 现在已经不安全了,优先级反转
- 现在推荐使用,pthread的mutex
- 专门用于轻量级数据访问,简单的int值 +1/-1操作
- 从系统的runtime去学习,工作中很少用
-
NSLock
- 加锁
- 逻辑
- 解锁
-
NSRecursiveLock
- 递归锁,可以重入
- 加锁和解锁是成对出现的。
-
dispatch_semaphore_t
- 信号量,记录型信号量
- create操作
- struct semaphore
- int value
- list <thread>
- struct semaphore
- wait -1 2个参数 信号量是哪个 等待时长
- 内部操作是, value - 1
- 当value小于0的时候 阻塞是一个主动行为
- signal +1
- value +1
- value 仍然《=0 说明在释放信号之前还有线程还在排队,然后就会去把相应的线程唤醒,这样就可以实现一个线程同步,唤醒是一个被动的行为。由释放信号的线程来唤醒被阻塞的线程
多线程面试问题总结
怎么用GCD实现多读单写?
dispatch_barrier_async的使用
iOS系统为我们提供了几种多线程技术各自的特点
NSthread
实现常驻线程。
GCD
实现一些简单的线程同步,子线程的分派,多读单写
NSOperation和NSOperationQueue
第三方框架 afn sdweb
任务状态控制
添加依赖
NSOperation 对象在Finished之后是怎么样从Queue中当移除掉的
- 通过KVO实现的,去通知Queue去移除 NSOperation
你都用过哪些锁?结合实际谈谈你是怎样使用的?
- NSLock
- NSRecursiveLock
- dispatch_smphore_t
- synchchronized
- atomic