一、基本概念
- 进程:一个独立运行的程序,关于某个数据集合的一次运行活动。在iOS中可以理解为一个运行的APP。在一个进程中可以包含多个线程。
- 线程:程序执行的最小单元,线程是进程中的一个实体。
- 同步:在当前线程中按照先后顺序依次执行任务,不开启新的线程。同步可以看成是单线程操作,会造成线程的阻塞。
- 异步:在当前线程中开启多个新线程执行,不一定按照先后顺序。异步操作一定是开启多个线程。
- 队列:装载线程任务的队形结构,队列可分为并发队列和串行队列。
- 并发:多个线程可以同时一起执行。
- 串行:线程执行只能按照一定的顺序,依次逐一先后有序的执行。
二、iOS中几种多线程对比
1. pthread
- 一套C语言实现的通用的多线程API
- 适用于Unix/Linux/Windows等操作系统
- 可移植,使用难度略大
- 生命周期由程序员手动管理
2. NSThread
- 由OC实现的面对对象的多线程
- 操作较为简单,可以直接操作多线程对象
- 生命周期由程序员手动管理
3. GCD
- C实现的替代NSThread的多线程技术
- 操作简单,易用
- 系统自动管理生命周期
4. NSOperation
- 基于GCD(底层是GCD实现),OC实现的多线程对象
- 比GCD多了一些实用功能,使用更加面向对象
- 系统自动管理生命周期
三、使用方法
1. pthread
iOS中很少直接调用pthread_create来创建线程,所以这里引用他人的一篇文章,里面关于线程的创建、管理以及互斥锁的使用做了详细的介绍。这里是连接
2. NSThread
- NSThread创建线程
// 方法1 创建之后需要手动启动
NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(doSomething1:) object:@"NSThread1"];
[thread1 start];
// 方法2 创建之后自动启动
[NSThread detachNewThreadSelector:@selector(doSomething2:) toTarget:self withObject:@"NSThread2"];
[NSThread detachNewThreadWithBlock:^{
// do something
}];
// 方法3 隐式创建,直接启动
[self performSelectorInBackground:@selector(doSomething3:) withObject:@"NSThread3"];
- NSThread的类方法
/* 1. 获取当前线程 */
[NSThread currentThread];
int num = [NSThread currentThread];
//num = 1,表示在主线程,否则是在子线程。
/* 2. 阻塞线程 */
// 线程进入休眠,2s后开启
[NSThread sleepForTimeInterval:2];
// 线程休眠到某个时间开启
[NSThread sleepUntilDate:[NSDate date]];
/* 3. 其他方法*/
//退出线程
[NSThread exit];
//判断当前线程是否为主线程
[NSThread isMainThread];
//判断当前线程是否是多线程
[NSThread isMultiThreaded];
//主线程的对象
NSThread *mainThread = [NSThread mainThread];
- NSThread的属性
//线程是否在执行
thread.isExecuting;
//线程是否被取消
thread.isCancelled;
//线程是否完成
thread.isFinished;
//是否是主线程
thread.isMainThread;
//线程的优先级,取值范围0.0到1.0,默认优先级0.5,1.0表示最高优先级,优先级高,CPU调度的频率高
thread.threadPriority;
3. GCD
GCD的使用步骤
- 创建队列(串行队列或并行队)
- 将任务添加到队列中,系统会根据任务类型执行任务(同步执行或者异步执行)
GCD的具体使用方法
创建队列的方法
- 可以使用dispatch_queue_create来创建对象,需要传入两个参数,第一个参数表示队列的唯一标识符,用于DEBUG,可为空;第二个参数用来识别是串行队列还是并行队列。DISPATCH_QUEUE_SERIAL表示串行队列,DISPATCH_QUEUE_CONCURRENT表示并行队列。
// 串行队列创建方法 dispatch_queue_t queue = dispatch_aueue_create("testQueue", DISPATCH_QUEUE_SERIAL); // 并行队列的创建方法 dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);
- 对于并行队列,还可以使用dispatch_get_global_queue来创建全局并行队列。GCD默认提供了全局的并行队列,需要传入两个参数。第一个参数表示队列优先级,一般用DISPATCH_QUEUE_PRIORITY_DEFAULT。第二个参数暂时没用,用0即可。
添加任务的方法
// 同步执行任务创建方法 dispatch_sync(queue, ^{ NSLog(@"%@",[NSThread currentThread]); // 这里放任务代码 }); // 异步执行任务创建方法 dispatch_async(queue, ^{ NSLog(@"%@",[NSThread currentThread]); // 这里放任务代码 });
队列和任务的不同的组合方式
并行队列 串行队列 主队列 同步(sync) 没有开启新线程,串行执行任务 没有开启新线程,串行执行任务 没有开启新线程,串行执行任务 异步(async) 有开启新线程,并行执行任务 有开启新线程(1条),串行执行任务 没有开启新线程,串行执行任务
4.NSOperation
- NSOperation实现步骤
- 实例化NSOperation的子类,绑定执行的操作
- 创建NSOperationQueue队列,将NSOperation实例添加到队列中
- 系统将自动检测NSOperation中的NSOperation对象并执行
NSOperation具体使用方法
NSOperation是抽象类,在实际运用的时候要用到它子类:NSInvocationOperation,NSBlockOperation,或者自定义子类继承NSOperation,实现内部相应的方法(这里不做介绍,自定义类的详细介绍点这里)
- NSInvocationOperation单独使用
// 创建NSInvocationOperation NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperation) object:nil]; // 开始执行操作 [invocationOperation start]; // 注意:默认情况下,调用了start方法后并不会开一条新线程去执行操作,而是在当前线程同步执行操作只有将NSOperation放到一个NSOperationQueue中,才会异步执行操作
- NSBlockOperation单独使用
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ // 在主线程 NSLog(@"下载1------%@", [NSThread currentThread]); }]; // 添加额外的任务(在子线程执行) [operation addExecutionBlock:^{ NSLog(@"下载2------%@", [NSThread currentThread]); }]; [operation addExecutionBlock:^{ NSLog(@"下载3------%@", [NSThread currentThread]); }]; [operation addExecutionBlock:^{ NSLog(@"下载4------%@", [NSThread currentThread]); }]; [operation start]; // 注意:只要NSBlockOperation封装的操作数 >1,就会异步执行操作
- NSInvocationOperation/NSBlockOperation配合NSOperationQueue使用
NSOperationQueue *qu = [[NSOperationQueue alloc] init]; NSBlockOperation * bkOp1 = [NSBlockOperation blockOperationWithBlock:^{ sleep(10); NSLog(@"bkOp1.....handle.....on thread num%@",[NSThread currentThread]); }]; [bkOp1 setCompletionBlock:^{ NSLog(@"bkOp1........OK !!"); }]; NSBlockOperation * bkOp2 = [NSBlockOperation blockOperationWithBlock:^{ sleep(2); NSLog(@"bkOp2.....handle.....on thread num%@",[NSThread currentThread]); }]; [bkOp2 setCompletionBlock:^{ NSLog(@"bkOp2........OK !!"); }]; NSBlockOperation * bkOp3 = [NSBlockOperation blockOperationWithBlock:^{ sleep(1); NSLog(@"bkOp3.....handle.....on thread num%@",[NSThread currentThread]); }]; [bkOp3 setCompletionBlock:^{ NSLog(@"bkOp3........OK !!"); }]; NSBlockOperation * bkOp4 = [NSBlockOperation blockOperationWithBlock:^{ sleep(10); NSLog(@"bkOp4.....handle.....on thread num%@",[NSThread currentThread]); }]; [bkOp4 setCompletionBlock:^{ NSLog(@"bkOp4........OK !!"); }]; NSBlockOperation * bkOp5 = [NSBlockOperation blockOperationWithBlock:^{ sleep(5); NSLog(@"bkOp5.....handle.....on thread num%@",[NSThread currentThread]); }]; [bkOp5 setQueuePriority:NSOperationQueuePriorityHigh]; [bkOp5 setCompletionBlock:^{ NSLog(@"bkOp5........OK !!"); }]; NSInvocationOperation *invoOp6 = [[NSInvocationOperation alloc] initWithTarget:(id)self selector:@selector(handleInvoOp) object:nil]; [invoOp6 setCompletionBlock:^{ NSLog(@"invoOp6........OK !!"); }]; [invoOp6 setQueuePriority:NSOperationQueuePriorityHigh]; [qu setMaxConcurrentOperationCount:2]; [qu addOperation:bkOp3]; [qu addOperation:bkOp2]; [qu addOperation:bkOp1]; [qu addOperation:bkOp4]; [qu addOperation:bkOp5]; [qu addOperation:invoOp6]; //注意:NSOperation可以调用start方法来执行任务,但默认是同步执行的,如果将NSOperation添加到NSOperationQueue(操作队列)中,系统会自动异步执行NSOperation中的操作
NSOperation之间的操作依赖
- NSOperation之间可以设置依赖来保证执行顺序,比如一定要让操作A执行完后,才能执行操作B,可以这么写
[operationB addDependency:operationA];// 操作B依赖于操作A
- 举例说明,下载->解压->更新UI
/** 举例场景: 1. 下载一个小说的压缩包 2. 解压缩,删除压缩包 3. 更新UI */ NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"下载一个小说的压缩包,%@",[NSThread currentThread]); }]; NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"解压缩,删除压缩包,%@",[NSThread currentThread]); }]; NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"更新UI,%@",[NSThread currentThread]); }]; // 如果直接把任务添加到队里,会异步执行,使顺序错乱 // 因此需要指定任务之间的依赖关系--------依赖关系可以跨队列(可以在子线程下载完,到主线程更新UI) [op2 addDependency:op1]; // op2 依赖于op1 就是执行op2之前必须先执行op1 [op3 addDependency:op2]; // op3 依赖于op2 就是执行op3之前必须先执行op2 /** 千万注意:不要造成相互依赖即依赖循环,会造成死锁 */ // [op1 addDependency:op3]; // waitUntilFinished 类似于GCD中调度组的通知 // NO表示不等待当前的队列执行完毕,就执行下面的代码,打印 NSLog(@"任务完成"); // YES 表示必须等队列内的任务全部执行完毕才执行下面的代码 [ self.opQueue addOperations:@[op1, op2] waitUntilFinished:YES]; // 在主线程更新UI [[NSOperationQueue mainQueue] addOperation:op3]; NSLog(@"任务完成");
NSOperation线程间通讯
[[[NSOperationQueue alloc] init] addOperationWithBlock:^{ // 图片的网络路径 NSURL *url = [NSURL URLWithString:@"http://img.pconline.com.cn/images/photoblog/9/9/8/1/9981681/200910/11/1255259355826.jpg"]; // 加载图片 NSData *data = [NSData dataWithContentsOfURL:url]; // 生成图片 UIImage *image = [UIImage imageWithData:data]; // 回到主线程 [[NSOperationQueue mainQueue] addOperationWithBlock:^{ self.imageView.image = image; }]; }];
四、参考文章
感谢分享
https://www.jianshu.com/p/6e6f4e005a0b