在讲多线程之前,先来熟悉几个概念。
1.串行(Serial)与并行(Concurrent)
串行时一次只能执行一个任务,并行时多个任务在同时执行。此处的任务可以理解为object-c的block。
2.同步(Synchronous)与异步(Asynchronous)
简单来说:同步会阻塞当前线程,异步不会,异步重现开一个线程,将任务丢到新开的线程中去执行。
3.线程安全
一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,就说线程安全的。反之,结果存在二义性,就是非线程安全的。
线程安全问题多半是由全局变量和静态变量引起的。一般用互斥锁解决线程不安全的问题。
@synchronized(锁对象) {
// 将线程不安全的代码写在这里
}
4.上下文切换-Context Switch
一个上下文切换指当你在单个进程里切换执行不同的线程时存储与恢复执行状态的过程
5.任务
任务即操作,其实就是一段具体做某件事情的代码,在GCD中就是一个block,任务有两种执行方式:同步执行和异步执行
*同步执行(sync):会阻塞当前线程并等待block中的任务执行完毕,然后当前线程才会继续执行下去;
*当前线程会继续往下执行,不会阻塞当前线程>
6.队列-Queues
队列是一种遵循先进先出FIFO的线性表。队列用于存放任务,GCD提供dispatch queues采用FIFO的顺序来处理这些代码块。多有的调度队列自身都是现场安全的。队列有分为串行队列和并行队列
。
*串行队列:串行队列中的任务GCD会根据FIFO(先进先出)的原则,取出一个,执行一个。
*并行队列:并行队列中的任务GCD依然使用FIFO原则,不同的是,GCD把取出来的任务放到一个个不同的线程中去执行,由于速度很快,所以看起来所有的任务都是一起执行的。也就是同一时间内可以有多个任务被执行。`
| 同步执行 | 异步执行 |
---------| ------------- |------
串行队列 | 当前线程,一个一个执行 |其它线程,一个一个执行
并行队列 | 当前线程,一个一个执行 | 开多个线程,一起执行
进程线程和任务之间的关系
可以看到一个进程process可以包含多个线程thread,每个线程又可以同时执行多个任务tasks。
ios中的多线程方案有Pthread,NSThread,GCD,NSOperation&NSOperationQueue
1.Pthread
据说没什么卵用,那就不管吧,😄
2.NSThread
3.NSOperation
NSOperation是对GCD的封装,是面向对象的,使用时直接控制线程对象即可,比较方便,但是NSOperation的生命周期需要手动管理。相比GCD,NSOperation更易于操作一个线程的暂停,取消或挂起。
NSOperation只是一个抽象的类,因此不能封装任务,具体的执行由它的两个子类
操作:
1.将要执行的任务封装到NSOperation对象中;
2.将该任务添加到NSOperationQueue对象中;
4.GCD
Grand Central Dispatch,可以将应用程序需要执行的工作拆分成多个可以分散到多个线程中的小块。GCD能自动管理线程的生命周期(创建线程,任务调度,销毁线程),使用时只需要把要做的工作添加进去就可以了,但是如果你想让该线程暂停,挂起或者取消它的运行就要需要增加更多的工作了。
系统提供的dispatch方法
1.后台执行
// 默认优先级,在高优先任务完成后,低优先级任务未开始前执行
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
});
// 最低优先级,队列中拥有最低优先级的任务会较默认优先级和最高优先级的任务之后执行
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
});
// 最高优先级,表示队列中拥有最高优先级的任务会较默认优先级和低优先级的任务之前执行
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
});
2.主线程执行
dispatch_async(dispatch_get_main_queue(), ^{
});
3.一次执行,保证线程安全
最常见的用法就是单利了,当多个对象对单利同时进行初始化,读或者写是就容易导致线程的不安全。dispatch_once可以保证目标任务(代码)只被执行一次。
+(mySingleton *)sharedInstance{
static mySingleton *single = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
single = [[mySingleton alloc] init]; // 保证single实例只被初始化一次
});
return single;
}
4.延迟执行
指定某个时间点后执行的队列,有点类似定时器。将任务延迟到某个时刻再执行
double delaySeconds = 1.0;
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delaySeconds * NSEC_PER_SEC));
dispatch_after(time, dispatch_get_main_queue(), ^{
});
5.自定义的串行或者并行队列。
// 自定义串行队列
dispatch_queue_create("elena1", DISPATCH_QUEUE_SERIAL); // 参数“elena1”是一个标识符,用于debug的时候标识唯一的队列,可以为空,DISPATCH_QUEUE_SERIAL 或 NULL 表示创建串行队列
// 自定义并行队列
dispatch_queue_create("elena2", DISPATCH_QUEUE_CONCURRENT);// 标识符"elena2",DISPATCH_QUEUE_CONCURRENT表明这是一个并行队列
6.一些高级用法
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 并行执行的线程A
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
// 并行执行的线程B
});
dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 线程A和线程B完成后,汇总结果
});
具体使用
情景举例:UserLocationController是一个专门用于定位及与位置相关操作的控制器,它有两个代理方法,定位成功及定位失败。
// 定位成功
-(void)locationSuccess:(NSDictionary *)dic{
[self useLocationToGetData:dic];// 定位成功
}
// 定位失败
-(void)locationFailure:(NSError *)error{
[self actionFailure:error];
}
/**使用定位的控制器***/
- (void)viewDidLoad
{
[super viewDidLoad];
[self function1];
[self location];
[self function2];
}
-(void)function1{
NSLog(@"%s",__func__);
UIButton *btn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
btn.frame = CGRectMake(100, 100, 70, 30);
[btn setBackgroundColor:RED];
[btn setTitle:@"click" forState:UIControlStateNormal];
[btn addTarget:self action:@selector(btnClick) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btn];
}
-(void)function2{
NSLog(@"%s",__func__);
}
-(void)btnClick{
NSLog(@"%s",__func__);
}
-(void)location{
NSLog(@"%s",__func__);
// 方式一
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UserLocationController *locationVC = [[UserLocationController alloc] initWithLocateToCurrentAdd:YES];
locationVC.view.frame = self.view.bounds;
NSLog(@"开始定位");
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"定位完成,更新UI");
locationVC.delegate=self;
});
});
/*结果:function1,location和function2函数被依次执行,location被添加到global_queue线程中执行,定位完成后更新UI。在定位的过程中,按钮依然可以点击,整个过程不会因为定位而导致程序卡主不动*/
// 方式二
dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL); // 同步串行队列
dispatch_sync(serialQueue, ^{
UserLocationController *locationVC = [[UserLocationController alloc] initWithLocateToCurrentAdd:YES];
locationVC.view.frame = self.view.bounds;
locationVC.delegate = self;
NSLog(@"定位完成");
});
/*结果:function1,location和function2函数被依次执行,定位操作被放入到自定义的串行队列中,并且是同步执行的,因此在定位完成前按钮不可以点击,整个过程因为定位而导致程序卡主不动*/
// 方式三
dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_CONCURRENT); // 并行串行队列
dispatch_async(serialQueue, ^{
UserLocationController *locationVC = [[UserLocationController alloc] initWithLocateToCurrentAdd:YES];
locationVC.view.frame = self.view.bounds;
locationVC.delegate = self;
NSLog(@"location finish");
});
/*结果:定位操作被添加到一个自定义的异步并行队列中处理,在定位完成前,按钮可点击,效果与方式一相同*/
// 方式四
double delaySeconds = 20.0;
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delaySeconds * NSEC_PER_SEC));
dispatch_after(time, dispatch_get_main_queue(), ^{
UserLocationController *locationVC = [[UserLocationController alloc] initWithLocateToCurrentAdd:YES];
locationVC.view.frame = self.view.bounds;
locationVC.delegate = self;
NSLog(@"finish");
});
/*结果:定位操作被延迟到20秒后执行,而且是被添加到了主线程中,毫无疑问,按钮是可以响应的。这个例子毫无用处,仅仅是个举例,你肯定希望定位操作尽快友好的完成*/
}
相关阅读:
https://www.raywenderlich.com/60749/grand-central-dispatch-in-depth-part-1