1、多线程简介:用户使用APP的时候如果每个操作都会等待好长的时间,才能得到程序的响应,这就影响了用户体验,为了减少用户的等待时间,我们可以使用多线程:开辟出子线程去处理一些比较耗时的操作(例如加载数据),这样减少了用户的等待时间提升了用户体验,但是开辟线程会占用一定的内存,(主线程所占内存大小是1M,子线程所占内存大小都是512KB,并且该值不能通过编译器开关或线程API函数来更改)降低程序的性能。所以一般情况下不要同时开辟过多线程。
2、使用多线程可能会出现的几个问题:多个线程同时访问共享的数据发生Critical Section、Race Condition;一个进程中多个线程之间的来回切换Context Switch,产生额外开销;为防止多线程同时访问时争抢造成的数据安全问题,需要使用互斥锁(但是需要消耗大量的资源);两个(多个)线程都要等待对方完成某个操作才能进行下一步,这时就会发生死锁。
多线程的实现有多种方式:NSThread、NSObject、NSOperation和GCD,其中较常用的是GCD,首先是几种多线程之间优缺点比较(前人总结直接粘过来的)
- NSThread (抽象层次:低)
- 优点:轻量级,简单易用,可以直接操作线程对象
- 缺点: 需要自己管理线程的生命周期,线程同步。线程同步对数据的加锁会有一定的系统开销。
- NSOperation (抽象层次:中)
- 优点:不需要关心线程管理,数据同步的事情,可以把精力放在学要执行的操作上。基于GCD,是对GCD 的封装,比GCD更加面向对象
- 缺点: NSOperation是个抽象类,使用它必须使用它的子类,可以实现它或者使用它定义好的两个子类NSInvocationOperation、NSBlockOperation.
-
GCD 全称Grand Center Dispatch (抽象层次:高)
- 优点:是 Apple 开发的一个多核编程的解决方法,简单易用,效率高,速度快,基于C语言,更底层更高效,并且不是Cocoa框架的一部分,自动管理线程生命周期(创建线程、调度任务、销毁线程)。
-
缺点: 使用GCD的场景如果很复杂,就有非常大的可能遇到死锁问题。
下面分别介绍一下基础操作:
首先NSThread开辟子线程:
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(sayHi) object:nil];//手动开启子线程
[thread start];
[NSThread exit];//使用NSThread和NSObject实现的开辟线程,系统会自动释放,关不关都行
NSThread自动开辟子线程:[NSThread detachNewThreadSelector:@selector(sayHi) toTarget:self withObject:nil];
#pragma mark - NSObject 开启子线程
[self performSelectorInBackground:@selector(sayHi) withObject:@"test"];//开辟子线程:sayHi方法在子线程中实现
[self performSelectorOnMainThread:@selector(mainThreadChangeColor) withObject:nil waitUntilDone:YES];//返回到主线程
#pragma mark - NSOperation——————NSOperation不能直接进行多线程的创建,需要借助:NSOperationQueue
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(test) object:nil];//使用NSOperarion的第一个子类去创建多线程:NSInvocationOperation
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"我是子线程");
}];//使用NSOperation 的第二个子类创建子线程(这里可以只使用其中一个子类):若单独使用(不使用NSOperationQueue,只开辟一个子线程)需要添加开启方法:[blockOperation start];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:operation];
[queue addOperation:blockOperation];
#pragma mark - GCD
dispatch_queue_t queue = dispatch_queue_create(DISPATCH_QUEUE_SERIAL, 0);//开辟子线程:串行队列
dispatch_queue_t queue = dispatch_queue_create(0, DISPATCH_QUEUE_CONCURRENT);//开辟子线程:并行队列,串行与并行任选一个
//添加任务(若有多个任务时)//创建串行队列:DISPATCH_QUEUE_SERIAL串行:任务顺序执行;DISPATCH_QUEUE_CONCURRENT并行:任务没有次序
dispatch_async(queue, ^{
NSLog(@"currentThread1 == %@, mainThread1 == %@, ismain == %d", [NSThread currentThread], [NSThread mainThread], [NSThread isMainThread]);//这里的部分将会在子线程下实现
dispatch_async(dispatch_get_main_queue(), ^{
//返回主线程。这里的部分将会在主线程下实现。一般数据的解析加载在子线程中实现,UI的刷新在主线程中实现,最后会举了简单的小例子说明一下
});
});
#++++++解析数据并刷新UI
//url
NSURL *url = [NSURL URLWithString:@"http://www.baidu.com"];
//创建请求
NSURLRequest *request = [NSURLRequest requestWithURL:url];
//创建session
NSURLSession *session = [NSURLSession sharedSession];
//创建task
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error == nil) {
//处理数据;这里是使用异步block自动创建的子线程,在这里处理数据,防止主线程出现卡顿
//回到主线程刷新UI
dispatch_async(dispatch_get_main_queue(), ^{
});
}
}];
NSOperationQueue与 GCD的比较:
NSOperationQueue使用addOperation方法将NSOperation类型的对象加入队列,并且该NSOperation类型的对象是NSOperation的子类创建的对象(NSInvocationOperation和NSBlockOperation);使用NSOperationQueue时不能出现start方法;
GCD:使用dispatch_queue_create方法创建子线程:根据该方法内的参数的不同从而创建串行队列(DISPATCH_QUEUE_SERIAL)和并行队列(DISPATCH_QUEUE_CONCURRENT);
使用dispatch_async添加任务;
CGD的应用比较广泛,在这里重点介绍一下:
常用的主线程串行队列异步执行任务,在主线程运行,不会产生死锁。
1 dispatch_queue_t mainQueue = dispatch_get_main_queue();
2 dispatch_async(mainQueue,^{
3 NSLog(@"ismain == %d", [NSThread isMainThread]);//isman == 1
4 });//只是在返回主线程的时候用,这里并不是创建子线程
1 dispatch_queue_t mainQueue = dispatch_queue_create(0, DISPATCH_QUEUE_SERIAL);
2 dispatch_async(mainQueue,^{
3 NSLog(@"ismain == %d", [NSThread isMainThread]);//isman == 0;注意与上边代码的区别 //创建了子线程
4 });
Global queue(全局并发队列)
//程序默认的队列级别(队列执行的优先级),DISPATCH_QUEUE_PRIORITY_DEFAULT == 0
dispatch_queue_t globalQueue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//HIGH
dispatch_queue_t globalQueue2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
//LOW
dispatch_queue_t globalQueue3 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
//BACKGROUND
dispatch_queue_t globalQueue4 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
1 dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);3 dispatch_sync(globalQueue, ^{//isman == 1;"sync"同步执行,未开辟子线程(可以简单理解为同步执行==未开辟子线程;异步执行==开辟子线程(特例除外))
4 sleep(2.0);//同步执行会出现卡顿,将“sync”改为“async”,isman == 0开辟了子线程
NSLog(@"ismain == %d", [NSThread isMainThread]);//当有多个任务时,异步执行无顺序;同步执行,先加入先执行
6 });
Custom queue (自定义串/并行队列)
dispatch_queue_t serialQueue = dispatch_queue_create("com.dullgrass.serialQueue", DISPATCH_QUEUE_SERIAL);//若想定义并行队列将“DISPATCH_QUEUE_SERIAL”改为DISPATCH_QUEUE_CONCURRENT
dispatch_async(serialQueue, ^{
NSLog(@"ismain == %d", [NSThread isMainThread]);
});
注意:注意不要嵌套使用同步执行的串行队列任务
1 dispatch_queue_t serialQueue = dispatch_queue_create("com.dullgrass.serialQueue", DISPATCH_QUEUE_SERIAL);
2 dispatch_sync(serialQueue, ^{ //无论该行是同步执行还是异步执行,红色代码部分都不会执行,
3 NSLog(@"会执行的代码");
4 dispatch_sync(serialQueue, ^{
5 NSLog(@"代码不执行");
6 });
7 });
dispatch_after延时添加到队列:
1 dispatch_time_t delayTime3 = dispatch_time(DISPATCH_TIME_NOW, 3*NSEC_PER_SEC);
2 dispatch_time_t delayTime2 = dispatch_time(DISPATCH_TIME_NOW, 2*NSEC_PER_SEC);
3 dispatch_queue_t mainQueue = dispatch_get_main_queue();
4 NSLog(@"current task");
5 dispatch_after(delayTime3, mainQueue, ^{
6 NSLog(@"3秒之后添加到队列");
7 });
8 dispatch_after(delayTime2, mainQueue, ^{
9 NSLog(@"2秒之后添加到队列");
10 });
11 NSLog(@"next task");
dispatch_apply在给定的队列上多次执行某一任务,在主线程直接调用会阻塞主线程去执行block中的任务。
- dispatch_apply函数的功能:把一项任务提交到队列中多次执行,队列可以是串行也可以是并行,dispatch_apply不会立刻返回,在执行完block中的任务后才会返回,是同步执行的函数。
- dispatch_apply正确使用方法:为了不阻塞主线程,一般把dispatch_apply放在异步队列中调用,然后执行完成后通知主线程
1 dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
2 NSLog(@"current task");
3 dispatch_async(globalQueue, ^{
4 dispatch_queue_t applyQueue = dispatch_get_global_queue(0, 0);
5 //第一个参数,3--block执行的次数
6 //第二个参数,applyQueue--block任务提交到的队列
7 //第三个参数,block--需要重复执行的任务
8 dispatch_apply(3, applyQueue, ^(size_t index) {
9 NSLog(@"current index %@",@(index));
10 sleep(1);
11 });
12 NSLog(@"dispatch_apply 执行完成");
13 dispatch_queue_t mainQueue = dispatch_get_main_queue();
14 dispatch_async(mainQueue, ^{
15 NSLog(@"回到主线程更新UI");
16 });
17 });
18 NSLog(@"next task");
最后是多线程单例的书写方式:(别忘了在.h中声明)
1 static MyHandle *myHandle = nil; 2 + (MyHandle *)shareMyHandle { 3 //在GCD中只执行一次,用于记录内容是否执行过 4 static dispatch_once_t onceToken; 5 dispatch_once(&onceToken, ^{ 6 myHandle = [[MyHandle alloc] init]; 7 }); 8 return myHandle; 9 }