iOS多线程总结
多线程 优缺点,实际应用
多线程比较
死锁:使用同步sync,向同一个/当前的串行队添加任务,会产生死锁
新等旧,旧等新
1- NSThread:
–优点:NSThread 比其他两个轻量级,使用简单
–缺点:需要自己管理线程的生命周期、线程同步、加锁、睡眠以及唤醒等。线程同步对数据的加锁会有一定的系统开销
2- NSOperation:
–不需要关心线程管理,数据同步的事情,可以把精力放在自己需要执行的操作上
–NSOperation是面向对象的
核心概念:把操作(异步)添加到队列(全局的并发队列)
OC 框架,更加面向对象,是对 GCD 的封装
iOS 2.0 推出的,苹果推出 GCD 之后,对 NSOperation 的底层全部重写
Operation作为一个对象,为我们提供了更多的选择
可以随时取消已经设定要准备执行的任务,已经执行的除外
可以跨队列设置操作的依赖关系
可以设置队列中每一个操作的优先级
高级功能:
最大操作并发数(GCD不好做)
继续/暂停/全部取消
跨队列设置操作的依赖关系
3- GCD:
–Grand Central Dispatch是由苹果开发的一个多核编程的解决方案。iOS4.0+才能使用,是替代NSThread, NSOperation的高效和强大的技术
–GCD是基于C语言的
将任务(block)添加到队列(串行/并发/主队列),并且指定任务执行的函数(同步/异步)
GCD是底层的C语言构成的API
iOS 4.0 推出的,针对多核处理器的并发技术
在队列中执行的是由 block 构成的任务,这是一个轻量级的数据结构
要停止已经加入 queue 的 block 需要写复杂的代码
需要通过 Barrier 或者同步任务设置任务之间的依赖关系
只能设置队列的优先级
高级功能:
一次性 once
延迟操作 after
调度组
比较
NSOperationQueue可以方便的管理并发、NSOperation之间的优先级。
GCD主要与block结合使用。代码简洁高效
- 性能:GCD更接近底层,而NSOperationQueue则更高级抽象,所以GCD在追求性能的底层操作来说,是速度最快的。这取决于使用Instruments进行代码性能分析,如有必要的话
- 从异步操作之间的事务性,顺序行,依赖关系。GCD需要自己写更多的代码来实现,而NSOperationQueue已经内建了这些支持
- 如果异步操作的过程需要更多的被交互和UI呈现出来,NSOperationQueue会是一个更好的选择。底层代码中,任务之间不太互相依赖,而需要更高的并发能力,GCD则更有优势
GCD的工作原理是:让程序平行排队的特定任务,根据可用的处理资源,安排在任何可用的处理器核心上执行任务。
一个任务可以是一个函数(function)或者是一个block。 GCD的底层依然是用线程实现,不过这样可以让程序员不用关注实现的细节。
GCD中的FIFO队列称为dispatch queue,它可以保证先进来的任务先得到执行
dispatch queue分为下面三种:
- Serial:又称为private dispatch queues,同时只执行一个任务。Serial queue通常用于同步访问特定的资源或数据。当你创建多个Serial queue时,虽然它们各自是同步执行的,但Serial queue与Serial queue之间是并发执行的。
- Concurrent:又称为global dispatch queue,可以并发地执行多个任务,但是执行完成的顺序是随机的。
- Main dispatch queue:它是全局可用的serial queue,它是在应用程序主线程上执行任务的。
dispatch_sync(),同步添加操作。等待添加进队列里面的操作完成之后再继续执行。调用以后等到block执行完以后才返回 ,dispatch_sync()会阻塞当前线程。
dispatch_async ,异步添加进任务队列,调用以后立即返回,它不会做任何等待
在多线程开发当中,程序员只要将想做的事情定义好,并追加到DispatchQueue(派发队列)当中就好了。派发队列分为两种,一种是串行队列(SerialDispatchQueue),一种是并行队列(ConcurrentDispatchQueue)。一个任务就是一个block,比如,将任务添加到队列中的代码是:dispatch_async(queue, block);当给queue添加多个任务时:
- queue是串行队列,则它们按顺序一个个执行,同时处理的任务只有一个。
- queue是并行队列时,不论第一个任务是否结束,都会立刻开始执行后面的任务,也就是可以同时执行多个任务。但是并行执行的任务数量取决于XNU内核,是不可控的。比如,如果同时执行10个任务,那么10个任务并不是开启10个线程,线程会根据任务执行情况复用,由系统控制。
- GCD组
- (void) gcd_group {
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
queue = dispatch_get_global_queue(0, 0);
dispatch_queue_t queue_1 = dispatch_queue_create("queue_1", DISPATCH_QUEUE_CONCURRENT);
queue_1 = dispatch_queue_create("SerialQueue", DISPATCH_QUEUE_SERIAL);
queue_1 = dispatch_get_global_queue(0, 0);
queue_1 = queue;
// 方式一
dispatch_group_async(group, queue, ^{
NSLog(@"组任务 1 - %@", [NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"组任务 2 - %@", [NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"组任务 3 - %@", [NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"组任务 4 - %@", [NSThread currentThread]);
});
// 方式二
// 在该方法后面的异步任务会被纳入到队列组的监听范围,进入群组
// dispatch_group_enter|dispatch_group_leave必须要配对使用
// 测试 当queue与queue_1 不一致是,组完成会提前完成
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"组任务 1 - %@", [NSThread currentThread]);
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"组任务 2 - %@", [NSThread currentThread]);
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"组任务 3 - %@", [NSThread currentThread]);
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"组任务 4 - %@", [NSThread currentThread]);
dispatch_group_leave(group);
});
// 第一种拦截方法
dispatch_group_notify(group, queue_1, ^{
NSLog(@"组完成 - %@", [NSThread currentThread]);
});
// dispatch_group_notify_f(group, queue, <#void * _Nullable context#>, <#dispatch_function_t _Nonnull work#>)
/**
第二种拦截方法
参数
1./队列组
2./时间
现在 DISPATCH_TIME_NOW : 传现在不会起到拦截作用,会马上执行
未来 DISPATCH_TIME_FOREVER: 等到队列组中的所有任务都执行完成后才会触发.也能起到监听队列组的效果
这个方法时阻塞的,队列组内任务不执行完成,下面的代码永远不会执行.
*/
// DISPATCH_TIME_NOW DISPATCH_TIME_FOREVER
dispatch_group_wait(group, DISPATCH_TIME_NOW);
// dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"--验证--");
// 执行task
dispatch_async_f(queue, (__bridge void * _Nullable)(@{@"1":@"2"}), task);
}
void task(void*param){
NSLog(@"%s - %@, param - %@",__func__,[NSThread currentThread], param);
}
- GCD栅栏障碍-任务分割 dispatch_barrier_async函数会等待前边追加到并发队列中的任务全部执行完毕之后,再将指定的任务追加到该异步队列中。然后在dispatch_barrier_async函数追加的任务执行完毕之后,异步队列才恢复为一般动作,接着追加任务到该异步队列并开始执行。
/**
* 栅栏方法 dispatch_barrier_sync / dispatch_barrier_async
*/
- (void)gcd_barrier {
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
// 追加任务1
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:1]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
}
});
dispatch_sync(queue, ^{
// 追加任务2
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:1];
NSLog(@"2---%@",[NSThread currentThread]);
}
});
// dispatch_barrier_sync
dispatch_barrier_async(queue, ^{
// 追加任务 barrier
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:1];
NSLog(@"barrier---%@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
// 追加任务3
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:1];
NSLog(@"3---%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
// 追加任务4
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:1];
NSLog(@"4---%@",[NSThread currentThread]);
}
});
}
多线程性能比较7.各种锁
@synchronized:适用线程不多,任务量不大的多线程加锁
NSLock:线程锁,其实NSLock并没有想象中的那么差,不知道大家为什么不推荐使用
dispatch_semaphore_t:使用信号来做加锁,性能提升显著
NSCondition:断言,使用其做多线程之间的通信调用不是线程安全的
NSConditionLock:条件锁,单纯加锁性能非常低,比NSLock低很多,但是可以用来做多线程处理不同任务的通信调用
NSRecursiveLock:递归锁/循环锁,性能出奇的高,但是只能作为递归使用,所以限制了使用场景
NSDistributedLock:分布式锁,MAC系统使用
POSIX(pthread_mutex):底层的api,复杂的多线程处理建议使用,并且可以封装自己的多线程
OSSpinLock:自旋锁,性能也非常高,可惜出现了线程问题,适合较短时间等待case
dispatch_barrier_async/dispatch_barrier_sync:dispatch_barrier_sync比dispatch_barrier_async性能要高,真是大出意外
Dispatch Semaphore 提供了三个函数:
- dispatch_semaphore_create:创建一个Semaphore并初始化信号的总量
- dispatch_semaphore_signal:发送一个信号,让信号总量加1
- dispatch_semaphore_wait:可以使总信号量减1,当信号总量为0时就会一直等待(阻塞所在线程),否则就可以正常执行。
信号量的使用前提:确定需要处理哪个线程等待(阻塞),需要哪个线程继续执行,然后使用信号量。
Dispatch Semaphore 在实际开发中主要用于:
保持线程同步,将异步执行任务转换为同步执行任务
保证线程安全,为线程加锁
如:AFNetworking 中 AFURLSessionManager.m 里面的 tasksForKeyPath: 方法。通过引入信号量的方式,等待异步执行任务结果,获取到 tasks,然后再返回该 tasks。
- (void)gcd_semaphoreSync {
NSLog(@"currentThread - %@",[NSThread currentThread]);
NSLog(@"semaphore - begin");
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block int var = 0;
dispatch_async(queue, ^{
// 任务1
[NSThread sleepForTimeInterval:1];
NSLog(@"1 currentThread - %@",[NSThread currentThread]);
var = 100;
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"semaphore - end,var = %d",var);
}
- (void) gcd_threadSafety {
NSLog(@"currentThread - %@",[NSThread currentThread]);
__block NSInteger ticketSurplusCount = 10;
dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_SERIAL);
dispatch_semaphore_t semaphoreLock = dispatch_semaphore_create(1);//
NSLock *myLock = [NSLock new];
NSCondition *myCondition = [NSCondition new];
void(^saleTicketSafe)(void) = ^ {
while (1) {
[myLock lock];
[myCondition lock];
@synchronized (self)
{
dispatch_semaphore_wait(semaphoreLock, DISPATCH_TIME_FOREVER);// 相当于加锁
if (ticketSurplusCount > 0) {
ticketSurplusCount--;
NSLog(@"%@", [NSString stringWithFormat:@"余票为:%ld 窗口:%@", (long)ticketSurplusCount, [NSThread currentThread]]);
[NSThread sleepForTimeInterval:0.2];
dispatch_semaphore_signal(semaphoreLock);// 相当于解锁
}
else {
NSLog(@"已售完");
dispatch_semaphore_signal(semaphoreLock);// 相当于解锁
break;
}
}
[myLock unlock];
[myCondition unlock];
}
};
dispatch_async(queue1, ^{
saleTicketSafe();
});
dispatch_async(queue2, ^{
saleTicketSafe();
});
}