内功心法-多线程的基本使用
一 ) 为什么使用多线程?
每个iOS应用程序都有个专门用来更新显示UI界面、处理用户的触摸事件的主线程,因此不能将其他太耗时的操作放在主线程中执行,不然会造成主线程堵塞(出现卡机现象),带来极坏的用户体验。一般的解决方案就是将那些耗时的操作放到另外一个线程中去执行,多线程编程是防止主线程堵塞,增加运行效率的最佳方法。
这里有两个概念是进程和线程。 进程就是负责程序运行的内存分配,而线程就是进程中得一个独立的执行那个路径。它是程序的执行路径,负责程序中代码的实际运行。这好比进程就是火车,线程就是火车的车厢,没有火车,车厢也跑不起来当然一个火车也不能只有一个车厢。
二) ios 中常用的三种多线程是什么?
1 Thread: 这是相对轻量级别的,抽象级别相对来说比较低,但是需要管理线程的生命周期,同步以及加锁,这会导致一定的性能开销
2 Operations: 这个是基于OC实现的,以面向对象的方式封装了需要执行的操作,我们可以不必关心线程的管理和同步的问题。它可以开始,取消线程执行。他有两个默认的实现方法:NSInvocationOperation和NSBlockOperation。当然我们也可以自定NSOperations,只有实现里面的main方法即可
3 GCD:也是重点讲的一个,它是ios4才开始使用的,当然现在我们ios9都出来了,GCD的成熟度足可以让我们放心的使用,它是基于C实现的,性能上要相对来说好一些,而且可以用最简单的代码去实现复杂的线程问题。
三) NSThread的基本使用方法
1.动态初始化
NSThread threadOne = [[NSThread alloc]initWithTarget:self selector:@selector(run) object:nil];
//其中run是一个方法,我们通过方法来执行多线程中得处理
[_threadOne setName:@"one"]; //给线程初始化一个别名
[_threadOne start];//开始执行
2 静态初始化
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil]; //通过这一句代码我们就可以创建出另一个线程出来并去执行
3 创建隐式线程:
[self performSelectorInBackground:@selector(run) withObject:nil];
注:这是NSObject的一个方法,他可以让一个耗时间的处理放入后台去执行,但是在swift中抛弃了,原因是苹果觉得这个方法是线程不安全的
4 获取当前的线程
NSThread *current = [NSThread currentThread];//返回的是目前的线程
5 返回主线程(刷新UI控件必须在主线程中执行)
[self performSelectorOnMainThread:@selector(main:) withObject:nil waitUntilDone:YES];
6 等待(暂停)当前的线程
[NSThread sleepForTimeInterval:3.0f] ;//3秒之后执行
或者
NSDate *date = [NSDate dateWithTimeInterval:2 sinceDate:[NSDate date]];
[NSThread sleepUntilDate:date]; 等待date完成后再去执行
注:前者是等待时间的完成,后者是等待数据的完成
四) NSOperation使用方法:
NSOperation 实例封装了需要执行的操作和执行操作所需的数据,并且能够以并发或非并发的方式执行这个操作。NSOperation在ios4后也基于GCD实现,但是相对于GCD来说可控性更强,并且可以加入操作依赖。NSOperation提供了ready cancelled executing finished这几个状态变化,我们的开发也是必须处理自己关心的其中的状态。这些状态都是基于keypath的KVO通知决定,所以在你手动改变自己关心的状态时,请别忘了手动发送通知。这里面每个属性都是相互独立的,同时只可能有一个状态是YES。finished这个状态在操作完成后请及时设置为YES,因为NSOperationQueue所管理的队列中,只有isFinished为YES时才将其移除队列,这点在内存管理和避免死锁很关键。
1.NSInvocationOperation:
NSInvocationOperation *op = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download:) object:nil];//红色多线程中处理的方法
[op start]; //开始
[op cancel];//取消
2 NSBlockOperation
NSBlockOperation *block = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@",[NSThread currentThread]); //处理线程的block方法
}];
3 NSOperationQueue
一个NSOperation对象可以通过调用start方法来执行任务,默认是同步执行的。也可以将NSOperation添加到一个NSOperationQueue(操作队列)中去执行,而且是异步执行的。一旦NSoperation添加到NSoperationQueue中,用户就无权对NSoperation管理,都有NSoperationQueue来执行。
3.1 添加一个队列
NSoperationQueue *myQueue = [[NSOperationQueue alloc]init];
NSInvocationOperation *op = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download:) object:nil];
[myQueue addOperation:op];
3.2 主队列(任何刷新UI的方法都必须在主队列中执行)
[[NSOperationQueue mainQueue]addOperationWithBlock:^{
//可以执行刷新UI控件的方法
}];
3.3 队列直接可以设置依赖,比如队列1需要队列2执行完毕后才能执行(addDependency)
NSBlockOperation *block1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
NSBlockOperation *block2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
[block1 addDependency:block2];
[myQueue addOperation:block1];
[myQueue addOperation:block2];
3.4可以设置最大并发的操作数量
[myQueue setMaxConcurrentOperationCount:2];
3.5 一旦添加到队列,队列就拥有了这个Operation对象并且不能被 删除,唯一能做的事情是取消。
[myQueue cancelAllOperations];
3.6获取NSOperation
myQueue.operations 这是一个数组,里面存放这添加进入这个队列的所有任务
3.7 如果你想临时暂停Operations的执行,可以使用queue的setSuspended:方法暂停queue。
[myQueue setSuspended:YES];
四)GCD
1 GCD是Grand Central Dispatch的简称,它是基于C语言的。如果使用GCD,完全由系统管理线程,我们不需要编写线程代码。只需定义想要执行的任务,然后添加到适当的调度队列(dispatch queue)。GCD会负责创建线程和调度你的任务,系统直接提供线程管理。
2 GCD的操作思想是讲操作放在队列中去执行
1 操作是用block来实现的
2 队列是先进先出的,它是负责调度任务执行所在的线程
3 GCD分为串行和并行,有自定义,主队列和全局队列三种。一个同步函数只在完成了它预定的任务后才返回。一个异步函数,刚好相反,会立即返回,预定的任务会完成但不会等它完成。因此,一个异步函数不会阻塞当前线程去执行下一个函数。如果在主队列中执行同步的话,会造成死锁的发生
3 串行和并行
串行:一次只能执行一个任务, 当前任务完成才开始出列并启动下一个任务
并行:则尽可能多地启动任务并发执行
4GCD基本使用方法
1 自定义队列
dispatch_queue_t q = dispatch_queue_create("gcdDemo1", DISPATCH_QUEUE_SERIAL); DISPATCH_QUEUE_SERIAL:串行,可以传入nil 默认是串行,"gcdDemo1”是这个线程的别名,可以传nil
dispatch_queue_t q = dispatch_queue_create("gcdDemo2", DISPATCH_QUEUE_CONCURRENT); DISPATCH_QUEUE_CONCURRENT:并行
dispatch_async(q, ^{ 注:异步执行,可以开辟多个线程去执行,无需等待
NSLog(@"%@",[NSThread currentThread]);
});
dispatch_sync(q, ^{ 注:同步执行,只开辟一个线程,需要等待上一个任务的完成才能执行
NSLog(@"%@",[NSThread currentThread]);
});
2 全局队列
dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
DISPATCH_QUEUE_PRIORITY_DEFAULT:默认的,全局队列是有优先级的
DISPATCH_QUEUE_PRIORITY_HIGH 最高(优先执行)
DISPATCH_QUEUE_PRIORITY_DEFAULT 默认
DISPATCH_QUEUE_PRIORITY_LOW 最低
DISPATCH_QUEUE_PRIORITY_BACKGROUND 后台
3 主队列
dispatch_queue_t q = dispatch_get_main_queue();
每一个应用程序都有一个主线程 在ios中所有的ui刷新都再主线程中执行!这是因为苹果为了提高性能,大部分库都是线程不安全的,如果在子线程刷新控件会造成一些问题,所有所有的UI控件的刷新都由主线程上刷新
4 延迟执行
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(), ^{
NSLog(@"我是3秒才执行的!");
});
5 dispatchGroup组队列
dispatchGroup作用:当 dispatch_group_async函数将多个任务关联到一个Dispatch Group和相应的queue中,group会并发地同时执行这些任务。而且Dispatch Group可以用来阻塞一个线程, 直到group关联的所有的任务完成执行。有时候你必须等待任务完成的结果,然后才能继续后面的处理。
dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, q, ^{
NSLog(@"1 == %@",[NSThread currentThread]);
});
dispatch_group_async(group, q, ^{
NSLog(@"2 == %@",[NSThread currentThread]);
});
dispatch_group_async(group, q, ^{
NSLog(@"3 == %@",[NSThread currentThread]);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{//不管前面的线程谁先执行,最后都会执行notify方法。
NSLog(@"4 == %@",[NSThread currentThread]);
});
6 dispathc_apply
dispathc_apply是dispatch_sync 和dispatch_group的关联API.它以指定的次数将指定的Block加入到指定的队列中。并等待队列中操作全部完成.
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(10, globalQueue, ^(size_t index) {
NSLog(@"%zu",index);
dispatch_source_merge_data(socurce, 1);
});
7 dispatch_source_t 信号源
dispatch source是一个监视某些类型事件的对象。当这些事件发生时,它自动将一个block放入一个dispatch queue的执行例程中。书中定义。我的理解就是多线程中得KVO,它检测用户事件,它是由dispatch_source_merge_data函数来向自己发送信号,然后通过dispatch_source_set_event_handler这个函数去执行。
这是我写了一个进度条的例子
执行部分: __weak __typeof(self)weakSelf = self;
dispatch_source_t socurce = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
dispatch_source_set_event_handler(socurce, ^{
[weakSelf.progressIndicator setProgress:dispatch_source_get_data(socurce) animated:YES];
});
dispatch_resume(socurce);
监听部分: dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(10, globalQueue, ^(size_t index) {
NSLog(@"%zu",index);
dispatch_source_merge_data(socurce, 1);
});
/*
dispatch_source_create(dispatch_source_type_t type,
uintptr_t handle,
unsigned long mask,
dispatch_queue_t queue);
第1个参数:要监听的事件类型
第2个参数:可以理解为句柄、索引或id,假如要监听进程,需要传入进程的ID
第3个参数:根据参数2,可以理解为描述,提供更详细的描述,让它知道具体要监听什么
第4个参数:当事件发生时,将block添加至哪个队列来执行
**/
8 dispatch_semaphore信号量
当我们在处理一系列线程的时候,当数量达到一定量,在以前我们可能会选择使用NSOperationQueue来处理并发控制,在GCD中我们需要通过dispatch_semaphore来控制它的并发数量。
dispatch_group_t group = dispatch_group_create();
dispatch_semaphore_t semaphore = dispatch_semaphore_create(5);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (int i = 0; i < 10; i++)
{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);//如果技术器的数值大于等于1的时候进行-1操作
dispatch_group_async(group, queue, ^{
NSLog(@"----%d",i);
sleep(2);
dispatch_semaphore_signal(semaphore);//计数器+1
});
}
简单的介绍一下这一段代码,创建了一个初使值为5的semaphore,每一次for循环都会创建一个新的线程,线程结束的时候会发送一个信号,线程创建之前会信号等待,所以当同时创建了5个线程之后,for循环就会阻塞,等待有线程结束之后会增加一个信号才继续执行,如此就形成了对并发的控制,如上就是一个并发数为5的一个线程队列。