一、应用场景
异步下载数据,这是多线程技术的一个比较常见的应用场景
还有一些比较耗时的操作或者功能(客户端与服务端的交互;从数据库中一次性读取大量数据等),需要在主线程之外,单独的开辟一个新的线程(子线程/工作线程)来执行。
二、iOS支持的多线程编程方法
NSThread
NSOperation & NSOperationQueue
GCD
四、线程的创建
创建后台线程,自动的开启线程
//performSelectorInBackground内部会创建一个线程 专门 执行调用 -thread1Click: [self performSelectorInBackground:@selector(thread1Click:) withObject:@"线程1"];
2.先创建,自己手动启动线程
NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(thread2Click:) object:@"线程2"]; //设置名字 thread2.name = @"thread2"; //启动线程 [thread2 start]; //这种方式创建线程 需要手动启动
3.线程一旦创建,会立即执行
[NSThread detachNewThreadSelector:@selector(thread3Click:) toTarget:self withObject:@"线程3"];
五、界面假死
在开发中常常需要将耗时操作交给子线程/工作线程,而主线程去刷新UI界面
- (void)viewDidLoad { [super viewDidLoad]; #if 0 [self func1]; [self func2]; [self func3]; /*上面的写法是把耗时操作都交给UI主线程来完成,这样主线程只有把上面三个函数执行完成 才会继续向下执行 这样的界面会假死,影响用户体验。 我们应该把耗时的操作交给子线程来完成,异步完成 */ #else //创建三个子线程 来完成 func1 func2 func3 [NSThread detachNewThreadSelector:@selector(func1) toTarget:self withObject:nil]; [NSThread detachNewThreadSelector:@selector(func2) toTarget:self withObject:nil]; [NSThread detachNewThreadSelector:@selector(func3) toTarget:self withObject:nil]; #endif } - (void)func1 { for (NSInteger i = 0; i < 20; i ++) { NSLog(@"func1:i->%ld",i); [NSThread sleepForTimeInterval:0.5]; } NSLog(@"func1即将结束"); } - (void)func2 { for (NSInteger i = 0; i < 20; i ++) { NSLog(@"func2:i->%ld",i); [NSThread sleepForTimeInterval:0.5]; } NSLog(@"func2即将结束"); } - (void)func3 { for (NSInteger i = 0; i < 20; i ++) { NSLog(@"func3:i->%ld",i); [NSThread sleepForTimeInterval:0.5]; } NSLog(@"func3即将结束"); }
六、监听线程结束
可以建立观察者,监听一个线程是否结束
- (void)viewDidLoad { [super viewDidLoad]; //要先注册观察者 //注册观察者的时候内部会专门创建一个线程监听其他线程有没有结束, //当线程结束时,一般都会发送一个结束通知 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(threadWillEnd:) name:NSThreadWillExitNotification object:nil]; //子线程 一旦创建启动 就会和主线程 同时异步执行 NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(thread1Click:) object:@"线程1"]; thread1.name = @"thread1"; [thread1 start]; NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(thread2Click:) object:@"线程2"]; thread2.name = @"thread2"; [thread2 start]; } - (void)thread1Click:(id)obj { //打印当前线程和主线程 NSLog(@"thread:%@ isMain:%d",[NSThread currentThread],[NSThread isMainThread]); NSLog(@"%s",__func__); for (NSInteger i = 0; i < 10; i ++) { NSLog(@"func1_i:%ld",i); [NSThread sleepForTimeInterval:1]; } NSLog(@"thread1即将结束"); } - (void)thread2Click:(id)obj { NSLog(@"thread:%@ isMain:%d",[NSThread currentThread],[NSThread isMainThread]); NSLog(@"%s",__func__); for (NSInteger i = 0; i < 10; i ++) { NSLog(@"func2_i:%ld",i); [NSThread sleepForTimeInterval:0.2]; } NSLog(@"thread2即将结束"); } //当监听到线程结束的时候调用这个函数 //上面的两个线程结束都会调用这个函数 - (void)threadWillEnd:(NSNotification *)nf { NSLog(@"thread:%@ isMain:%d_func:%s",[NSThread currentThread],[NSThread isMainThread],__func__); NSLog(@"obj:%@",nf.object);//谁发的通知 } - (void)dealloc { //最后要移除观察者 [[NSNotificationCenter defaultCenter] removeObserver:self name:NSThreadWillExitNotification object:nil]; }
七、多个线程之间的通信
- (void)viewDidLoad { [super viewDidLoad]; //创建3个线程 _thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(thread1Click:) object:@"线程1"]; [_thread1 start]; NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(thread2Click:) object:@"线程2"]; [thread2 start]; NSThread *thread3 = [[NSThread alloc] initWithTarget:self selector:@selector(thread3Click:) object:@"线程3"]; [thread3 start]; } - (void)thread1Click:(id) obj { NSLog(@"%s",__func__); while (1) { if ([[NSThread currentThread] isCancelled]) { //要判断 当前这个线程 是否 被取消过(是否发送过取消信号) //如果被取消过,那么我们可以让当前函数结束那么这个线程也就结束了 //break;//结束循环 NSLog(@"子线程1即将结束"); //return;//返回 函数 [NSThread exit];//线程退出 } NSLog(@"thread1"); [NSThread sleepForTimeInterval:0.5]; } NSLog(@"子线程1即将结束"); } - (void)thread2Click:(id) obj { NSLog(@"%s",__func__); for (NSInteger i = 0; i < 10; i++) { [NSThread sleepForTimeInterval:0.2]; NSLog(@"thread2:_i:%ld",i); } NSLog(@"子线程2即将结束"); //当 thread2 即将结束之后 通知 thread1结束 [_thread1 cancel]; // cancel 在这里 只是给_thread1 发送了一个cancel 信号,最终thread1的取消,取决于 thread1的,如果要取消thread1,那么需要在thread1执行调用的函数内部 进行判断 } - (void)thread3Click:(id) obj { NSLog(@"%s",__func__); for (NSInteger i = 0; i < 10; i++) { [NSThread sleepForTimeInterval:0.2]; NSLog(@"thread3:_i:%ld",i); } NSLog(@"子线程3即将结束"); }
八、线程锁
当多个线程同时操作同一个资源的时候,这时如果不处理,那么这个资源有可能就会紊乱,达不到我们想要的效果,所以如果我们要保证同时访问的重要数据不紊乱,我们需要添加线程锁,阻塞线程,使线程同步。排队访问
- (void)viewDidLoad { [super viewDidLoad]; //必须要先创建锁 _lock = [[NSLock alloc] init]; //创建两个线程 操作同一个资源变量 [NSThread detachNewThreadSelector:@selector(thread1Click:) toTarget:self withObject:@"线程1"]; [NSThread detachNewThreadSelector:@selector(thread2Click:) toTarget:self withObject:@"线程2"]; } - (void)thread1Click:(id)obj { #if 0 [_lock lock];//加锁 NSLog(@"thread1开始"); for (NSInteger i = 0 ; i < 10; i++) { _cnt += 2;//想让 _cnt 连续+2 NSLog(@"thread1_cnt:%ld",_cnt); [NSThread sleepForTimeInterval:0.2]; } NSLog(@"thread1即将结束"); [_lock unlock];//解锁 //访问资源结束解锁 #else // @synchronized (self){} 类似于 加锁和解锁过程 @synchronized(self) { //使线程对当前对象进行操作时,同步进行,阻塞线程 //跟加锁原理是一样的,执行 @synchronized(self)会判断有没有加锁,加过锁那么阻塞,没有加锁就继续执行 NSLog(@"thread1开始"); for (NSInteger i = 0 ; i < 10; i++) { _cnt += 2;//想让 _cnt 连续+2 NSLog(@"thread1_cnt:%ld",_cnt); [NSThread sleepForTimeInterval:0.2]; } NSLog(@"thread1即将结束"); } #endif } - (void)thread2Click:(id)obj { [_lock lock]; NSLog(@"thread2开始"); for (NSInteger i = 0 ; i < 10; i++) { _cnt -= 5;//让 _cnt连续-5 NSLog(@"thread2_cnt:%ld",_cnt); [NSThread sleepForTimeInterval:0.2]; } NSLog(@"thread2即将结束"); [_lock unlock]; }
九、任务队列
NSThread操作线程是最基本的类,NSOperation是一个轻量级的线程,任务队列得到的子线程的效率要高于NSTread。
NSOperation是以任务为导向的管理线程机制,将操作(任务)放入到线程池里,会自动执行,弱化线程的概念(任务:可以简单的理解为线程)
- (void)viewDidLoad { [super viewDidLoad]; //创建一个线程池 _queue = [[NSOperationQueue alloc] init]; //设置 一个队列中 允许 最大 任务的并发 个数 _queue.maxConcurrentOperationCount = 2; //如果写成1 表示 线程池中的任务 一个一个 串行执行 [self createInvocationOperation]; [self createBlockOperation]; } /* NSOperation 是一个抽象类 NSOperation 方法 需要有子类自己实现 //创建任务对象 都是 NSOperation 的子类对象 //NSBlockOperation NSInvocationOperation 任务 要和 任务队列/线程池 结合使用 */ #pragma mark - block任务 - (void)createBlockOperation { //block代码块就是一个 任务 NSBlockOperation *blockOp1 = [NSBlockOperation blockOperationWithBlock:^{ for (NSInteger i = 0; i < 10; i++) { NSLog(@"block任务:%ld",i); [NSThread sleepForTimeInterval:0.3]; } }]; [blockOp1 setCompletionBlock:^{ //block 任务完成之后 会回调 这个block NSLog(@"block任务完成"); }]; //放在线程池中 [_queue addOperation:blockOp1]; } #pragma mark - 任务 //第一种任务 - (void)createInvocationOperation { NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operation1:) object:@"任务1"]; //[op1 start]; 任务 默认start 相对于主线程是同步 //把任务 放入 线程池 [_queue addOperation:op1];//一旦放入 这个 任务 就会异步启动 NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operation2:) object:@"任务2"]; //把任务 放入 线程池 [_queue addOperation:op2]; NSInvocationOperation *op3 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operation3:) object:@"任务3"]; //把任务 放入 线程池 [_queue addOperation:op3]; } - (void)operation3:(id)obj { NSLog(@"obj:%@",obj); for (NSInteger i = 0; i < 10; i++) { [NSThread sleepForTimeInterval:0.5]; NSLog(@"op3:i->%ld",i); } NSLog(@"任务3即将结束"); } - (void)operation2:(id)obj { NSLog(@"obj:%@",obj); for (NSInteger i = 0; i < 10; i++) { [NSThread sleepForTimeInterval:0.5]; NSLog(@"op2:i->%ld",i); } NSLog(@"任务2即将结束"); } - (void)operation1:(id)obj { NSLog(@"obj:%@",obj); for (NSInteger i = 0; i < 10; i++) { [NSThread sleepForTimeInterval:0.5]; NSLog(@"op1:i->%ld",i); } NSLog(@"任务1即将结束"); }
十、GCD
GCD 全称Grand Central Dispatch(队列调度),是一套低层API,提供了⼀种新的方法来进⾏并发程序编写。从基本功能上讲,GCD有点像NSOperationQueue,他们都允许程序将任务切分为多个单一任务,然后提交⾄至⼯工作队列来并发地或者串⾏行地执⾏行。
GCD是C实现,⽐NSOpertionQueue更底层更高效,并且它不是Cocoa框架的一部分,并发任务会像NSOperationQueue那样基于系统负载来合适地并发进⾏,串⾏行队列同一时间只执行单一任务
GCD的API很大程度上基于block
- (void)viewDidLoad { [super viewDidLoad]; //[self createMainQueue]; //[self createPrivateQueue]; [self createGlobalQueue]; } #pragma mark - 全局队列 /* 3.全局队列 // 并行队列(全局)不需要我们创建,通过dispatch_get_global_queue()方法获得 // 三个可用队列 // 第一个参数是选取按个全局队列,一般采用DEFAULT,默认优先级队列 // 第二个参数是保留标志,目前的版本没有任何用处(不代表以后版本),直接设置为0就可以了 // DISPATCH_QUEUE_PRIORITY_HIGH // DISPATCH_QUEUE_PRIORITY_DEFAULT // DISPATCH_QUEUE_PRIORITY_LOW dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); */ - (void)createGlobalQueue { //全局队列 内部任务 异步/并行 执行 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ for (NSInteger i = 0; i < 10; i++) { [NSThread sleepForTimeInterval:0.5]; NSLog(@"全局队列任务1:%ld",i); } }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ for (NSInteger i = 0; i < 10; i++) { [NSThread sleepForTimeInterval:0.5]; NSLog(@"全局队列任务2:%ld",i); } }); } #pragma mark - 私有队列 /* 2.创建私有队列 用户队列/串行队列 // C接口,创建一个私有队列 ,队列名是一个C字符串,没有特别的要求,Apple建议用倒装的标识符来表示(这个名字,更多用于调试) 私有队列内部也是串行操作 */ - (void)createPrivateQueue { //创建一个私有队列 //私有队列 相对于主线程 异步 //私有队列内部的任务 是 串行执行 //下面函数的第一个参数 就是一个标签字符串 标识队列 dispatch_queue_t queue = dispatch_queue_create("com.1507", NULL); //增加任务 dispatch_async(queue, ^{ for (NSInteger i = 0; i < 10; i++) { [NSThread sleepForTimeInterval:0.5]; NSLog(@"私有队列任务1"); } }); //增加任务 这两个任务 是 ? dispatch_async(queue, ^{ for (NSInteger i = 0; i < 10; i++) { [NSThread sleepForTimeInterval:0.5]; NSLog(@"私有队列任务2"); } }); //私有队列 相当于 NSOperationQueue 队列 内部最大并发个数是1 } #pragma mark - 主线程队列 - (void)createMainQueue { //主线程队列 只需要获取 --》一般都是在子线程获取 在子线程获取才有意义 //1.获取主线程队列 -->主线程队列内部 都是 串行 dispatch_queue_t queue = dispatch_get_main_queue(); //dispatch_async 相对于 当前线程 异步给 queue 增加一个任务 dispatch_async(queue, ^{ //给主线程 增加一个任务 for (NSInteger i = 0; i < 10; i++) { NSLog(@"主线程任务1_i:%ld",i); [NSThread sleepForTimeInterval:0.5]; } }); //增加任务 是异步 的 主线程 队列内部 是串行执行任务的 dispatch_async(dispatch_get_main_queue(), ^{ for (NSInteger i = 0; i < 10; i++) { NSLog(@"主线程任务2_i:%ld",i); [NSThread sleepForTimeInterval:0.5]; } }); }