多线程:一个进程里面开启多条线程,每条线程可以单独的执行不同的任务。
iOS实现多线程的方式:
1、pthread(C写的、基本不用) 2、NSThread 3、gcd 4、NSOperation
下面分别介绍下后三个常用的多线程方式
NSThread:
使用方式
// 方式1
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(run) object:nil];
[thread start];// 开启
// 方式2
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
// 方式3
[self performSelectorInBackground:@selector(run) withObject:nil];
介绍NSThread,要首先介绍一下线程的生命周期:新建-就绪-运行-阻塞-死亡
优点:比较轻量,使用方式更加灵活,可以很直观的控制线程对象。例如直接取消线程,也可以自定线程。
缺点:需要自己管理线程的生命周期、线程同步。
解释一下线程同步:多条线程按顺序执行任务。NSThread通过加锁实现,加锁对系统资源有一定的消耗。
下面的两种方式不用关心线程管理,数据同步的问题。
GCD:
使用方式
dispatch_queue_t queue = dispatch_queue_create("queue.concurrent", DISPATCH_QUEUE_CONCURRENT); // 串行队列
dispatch_queue_t queue = dispatch_queue_create("queue.serial", DISPATCH_QUEUE_SERIAL); //并行队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);//获取全局的并发队列
dispatch_queue_t queue = dispatch_get_main_queue();//获取主队列
dispatch_sync(queue, ^{ // 同步函数 要求马上执行
//do something
});
dispatch_async(queue, ^{ // 异步函数 等主线程执行完,在开线程执行任务
// do something
});
GCD是苹果为多核并行运算提出的解决方案,会自动利用多核,自动管理线程的生命周期(创建,调度,销毁)。
那么GCD是如何自己管理生命周期和线程同步的问题呢,有两个概念 队列(queue) 和 任务 (task,上面的block),
使用方式已经在上面列出,下面总结一下两个函数和各种队列的使用效果:
首先是同步函数dispatch_sync,无论是并行队列还是串行队列,都不会开启新线程,并同步执行。
异步函数dispatch_async在串行(非主队列)或并行队列中都会开启新线程,不同的是串行队列里是串行执行任务,异步队列是并发执行任务。如果是在主队列,不开启新线程,串行执行任务。
下面说一下死锁的问题:
dispatch_sync有个特性是不等当前任务执行完成立即开启下个任务,如果下个任务还是在当前队列执行任务,就会造成相互等待(死锁)。
举个例子
// 当前在主队列里
dispatch_queue_t queue = dispatch_get_main_queue(); // 获取主队列
dispatch_sync(queue, ^{
NSLog(@"---download1---%@",[NSThread currentThread]);
});
//同步执行任务,这时候主队列停止,等待sync添加的任务,
而sync添加的任务是在当前队列里执行NSLog,
NSLog又要等当前的队列执行完上个任务才能执行,就陷入了相互等待。。。
线程之间通信:
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
// 异步执行任务
dispatch_async(dispatch_get_main_queue(), ^{ // 回到主队列
NSLog(@"%@",[NSThread currentThread]);
});
});
取消任务
iOS8之后可以调用dispatch_block_cancel
来取消(需要注意必须用dispatch_block_create
创建dispatch_block_t
)
dispatch_queue_t queue = dispatch_queue_create("com.test", DISPATCH_QUEUE_CONCURRENT);
dispatch_block_t block = dispatch_block_create(0, ^{
NSLog(@"block1 %@",[NSThread currentThread]);
});
dispatch_async(queue, block);
dispatch_block_cancel(block);
需要注意的是这种方式只能取消还没开始的任务
第二种取消方式就是模仿NSOperation里面的isCanceled。就是执行任务的时候加判断
dispatch_queue_t queue = dispatch_queue_create("com.test", DISPATCH_QUEUE_CONCURRENT);
__block BOOL isCancel = NO;
dispatch_async(queue, ^{
sleep(3);
if(isCancel){
// 任务取消了
}else{
// 没有取消,继续执行
}
});
NSOperation
NSOperation是对gcd的封装,面向对象,并多了一些简单的功能。
NSOperation和NSOperationQueue实现多线程的具体步骤
1.将需要执行的操作封装到一个NSOperation对象中
2.将NSOperation对象添加到NSOperationQueue中
系统会自动将NSOperationQueue中的NSOperation取出来,并将取出的NSOperation封装的操作放到一条新线程中执行
NSOperation是个抽象类,要想使用必须继承该类,系统提供了两个直接能用的类
NSInvocationOperation
NSInvocationOperation *op = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download) object:nil];
[op start];
NSBlockOperation
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"@",[NSThread currentThread]);
}];
[op addExecutionBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
[op start];
自定义类例如 MyOperation 继承NSOperation
实现main方法
NSOperationQueue:
NSOperation可以调用start方法来执行任务,但默认是同步执行的
如果将NSOperation添加到NSOperationQueue(操作队列)中,系统会自动异步执行NSOperation中的操作
下面是添加到queue的方法
- (void)addOperation:(NSOperation *)op;
- (void)addOperationWithBlock:(void (^)(void))block;
还有三个方法重点说一下
- maxConcurrentOperationCount 设置大的并发数
- suspended 暂停任务
- cancelAllOperations 取消所有任务,这里也是只能取消当前没有开始的任务。(要想取消当前的任务,需要在任务里随时判断isCanceled变量)
通过上面的总结,希望能回答两个问题(1、iOS实现多线程的方式,各自的特点,优缺点 2,多线程使用要注意什么,gcd为什么会造成死锁?)。
多线程应用的好,可以提升app的运行效率,流畅度,运用不好的话也会有许多的负面效果,比如开启线程是很消耗系统资源的,不能无限制开启,再比如死锁等问题。所以以后在项目里要灵活运用多线程。
(最近就遇到一个问题,搜索列表问题,随着用户的输入,实时显示搜索到的列表(本地数据,实时过滤,当然量比较大),如果只是运用gcd的get_global_queue实现多线程的话会有问题,因为任务回调回来的时机不同,会造成显示的列表不对。然后我就用了Operation来解决的这个问题,当来新的搜索任务也就是用户输入改变的时候,如果上个任务还没有完成,则取消之前的任务。)