基本概念
- 进程:进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,在iOS系统中,开启一个应用就打开了一个进程。
- 线程:线程(Thread)是进程中的一个实体,程序执行的基本单元。在iOS系统中,一个进程包含一个主线程,它的主要任务是处理UI事件,显示和刷新UI。
- 同步:在当前线程依次执行,不开启新的线程。
- 异步:多个任务情况下,一个任务A正在执行,同时可以执行另一个任务B。任务B不用等待任务A结束才执行。存在多条线程。
- 队列:存放任务的结构
- 串行:线程执行只能依次逐一先后有序的执行。
- 并发:指两个或多个事件在同一时间间隔内发生。可以在某条线程和其他线程之间反复多次进行上下文切换,看上去就好像一个CPU能够并且执行多个线程一样。其实是伪异步。
- 并行:指两个或多个时间在同一时刻发生。多核CUP同时开启多条线程供多个任务同时执行,互不干扰。
会出现的问题
- 临界代码段:指的是不能同时被两个线程访问的代码段,比如一个变量,被并发进程访问后可能会改变变量值,造成数据污染(数据共享问题)。
- 死锁:两个(多个)线程都要等待对方完成某个操作才能进行下一步,这时就会发生死锁。
- 线程安全:一段线程安全的代码(对象),可以同时被多个线程或并发的任务调度,不会产生问题,非线程安全的只能按次序被访问。
注意:
1.所有Mutable对象都是非线程安全的,所有Immutable对象都是线程安全的,使用Mutable对象,一定要用同步锁来同步访问(@synchronized)。
2.互斥锁:能够防止多线程抢夺造成的数据安全问题,但是需要消耗大量的资源
3.原子属性(atomic)加锁
4.atomic: 原子属性,为setter方法加锁,将属性以atomic的形式来声明,该属性变量就能支持互斥锁了。
5.nonatomic: 非原子属性,不会为setter方法加锁,声明为该属性的变量,客户端应尽量避免多线程争夺同一资源。
GCD中的队列类型
- The main queue(主线程串行队列): 与主线程功能相同,提交至Main queue的任务会在主线程中执行
- Global queue(全局并发队列): 全局并发队列由整个进程共享,有高、中(默认)、低、后台四个优先级别。
- Custom queue (自定义队列): 可以为串行,也可以为并发。
- Group queue (队列组):将多线程进行分组,最大的好处是可获知所有线程的完成情况。
Group queue 可以通过调用dispatch_group_create()来获取,通过dispatch_group_notify,可以直接监听组里所有线程完成情况。
The main queue(主线程串行队列)
- 获取主线程串行队列
dispatch_queue_t queue = dispatch_get_main_queue();
- 同步执行任务,在主线程运行时,会产生死锁
上面的代码执行之后,首先会阻塞当前线程(主线程),然后等待主线程执行完,在回到调用线程(主线程)继续执行。如果主线程被阻塞,那么NSLog将不会被执行,也就永远无法继续执行主线程了,dispatch_sync(queue,^{ NSLog("queue"); //这句代码永远不会执行,因为产生了死锁 });
程序一直处于等待状态,block中的代码将执行不到,造成了死锁
。 - 异步执行任务,在主线程运行,不会产生死锁。
打印内容dispatch_async(queue,^{ NSLog("dispatch_async main queue"); });
代码正常执行,没有产生死锁。2018-02-23 10:52:13 GCD队列[2273:235886] dispatch_async main queue
- 加载图片常用方式,返回主线程刷新UI
//以便在block中使用 __block UIImage *image = [[UIImage alloc] init]; //创建异步线程执行队列 dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); //dispatch_queue_t asynchronousQueue = dispatch_queue_create("imageDownloadQueue", NULL); //创建异步线程 dispatch_async(globalQueue, ^{ [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"userPhoto"]; NSError *error; NSData *dataPhoto = [NSData dataWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@",_photoPath]] options:NSDataReadingMappedIfSafe error:&error]; if (dataPhoto) { image = [UIImage imageWithData:dataPhoto]; //存头像 [[NSUserDefaults standardUserDefaults] setObject:dataPhoto forKey:@"userPhoto"]; } //回到主线程更新UI dispatch_async(dispatch_get_main_queue(), ^{ [imgView setImage:image]; }); });
- 主线程串行队列无法调用dispatch_resume()和dispatch_suspend()来控制执行继续或中断。
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);
- 同步执行任务,在主线程执行会导致页面卡顿。
打印结果dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); NSLog(@"current task"); dispatch_sync(globalQueue, ^{ sleep(2.0); NSLog(@"sleep 2.0s"); }); NSLog(@"next task");
2018-02-23 11:02:55 GCD队列[2286:238901] current task 2018-02-23 11:02:57 GCD队列[2286:238901] sleep 2.0s 2018-02-23 11:02:57 GCD队列[2286:238901] next task
可以看出sleep 2.0s等待了2秒之后才执行的 根据同步执行的特点(在当前线程依次执行,不开启新的线程),因此程序在等待了2秒之后,执行了sleep 2.0s打印
- 异步执行任务,在主线程运行,会开启新的线程去执行任务,页面不会卡顿。
打印结果dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); NSLog(@"current task"); dispatch_async(globalQueue, ^{ sleep(2.0); NSLog(@"sleep 2.0s"); }); NSLog(@"next task");
2018-02-23 11:09:53 GCD队列[2289:240672] current task 2018-02-23 11:09:53 GCD队列[2289:240672] next task 2018-02-23 11:09:55 GCD队列[2289:240717] sleep 2.0s
可以看出next task是先执行的 主线程不需要等待2s,可以继续执行后面的代码
- 多个并发队列,异步执行任务。
打印结果NSLog(@"current task"); for (int i = 0; i < 3; i++) { dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(globalQueue, ^{ NSLog(@"全局并发队列"); }); } NSLog(@"next task");
2018-02-23 11:15:46 GCD队列[2294:242439] current task 2018-02-23 11:15:46 GCD队列[2294:242439] next task 2018-02-23 11:15:46 GCD队列[2294:242486] 全局并发队列 2018-02-23 11:15:46 GCD队列[2294:242486] 全局并发队列 2018-02-23 11:15:46 GCD队列[2294:242486] 全局并发队列
Custom queue (自定义队列)
- 自定义串行队列
- 自定义串行队列同步执行任务
打印结果dispatch_queue_t serialQueue = dispatch_queue_create("com.xyy.serialQueue", DISPATCH_QUEUE_SERIAL); NSLog(@"当前任务"); dispatch_sync(serialQueue, ^{ NSLog(@"最先加入自定义串行队列"); sleep(2); }); dispatch_sync(serialQueue, ^{ NSLog(@"次加入自定义串行队列"); }); NSLog(@"下一个任务");
2018-02-23 11:21:09 GCD队列[2297:243862] 当前任务 2018-02-23 11:21:09 GCD队列[2297:243862] 最先加入自定义串行队列 2018-02-23 11:21:11 GCD队列[2297:243862] 次加入自定义串行队列 2018-02-23 11:21:11 GCD队列[2297:243862] 下一个任务
当前线程等待串行队列中的子线程执行完成之后再执行,串行队列中先进来的子线程先执行任务,执行完成后,再执行队列中后面的任务。
- 自定义并发队列
- 自定义并发队列执行同步任务
打印结果dispatch_queue_t conCurrentQueue = dispatch_queue_create("com.xyy.conCurrentQueue", DISPATCH_QUEUE_CONCURRENT); NSLog(@"当前任务"); dispatch_sync(conCurrentQueue, ^{ NSLog(@"先加入队列"); }); dispatch_sync(conCurrentQueue, ^{ NSLog(@"次加入队列"); }); NSLog(@"下一个任务");
2018-02-23 11:28:11 GCD队列[2300:245747] current task 2018-02-23 11:28:11 GCD队列[2300:245747] 先加入队列 2018-02-23 11:28:11 GCD队列[2300:245747] 次加入队列 2018-02-23 11:28:11 GCD队列[2300:245747] next task
- 自定义并发队列嵌套执行同步任务(不会产生死锁,程序正常运行)
打印结果dispatch_queue_t conCurrentQueue = dispatch_queue_create("com.xyy.conCurrentQueue", DISPATCH_QUEUE_CONCURRENT); NSLog(@"当前任务"); dispatch_sync(conCurrentQueue, ^{ NSLog(@"先加入队列"); dispatch_sync(conCurrentQueue, ^{ NSLog(@"次加入队列"); }); }); NSLog(@"下一个任务");
2018-02-23 11:30:54 GCD队列[2303:246697] current task 2018-02-23 11:30:54 GCD队列[2303:246697] 先加入队列 2018-02-23 11:30:54 GCD队列[2303:246697] 次加入队列 2018-02-23 11:30:54 GCD队列[2303:246697] next task
- 自定义并发队列执行异步任务
打印结果NSLog(@"当前任务"); for (int i = 0; i < 3; i++) { dispatch_queue_t conCurrentQueue = dispatch_queue_create("com.xyy.conCurrentQueue", DISPATCH_QUEUE_CONCURRENT); dispatch_async(conCurrentQueue, ^{ NSLog(@"自定义队列 %d %@",i,[NSThread currentThread]); }); } NSLog(@"下一个任务");
异步执行任务,开启子线程,不影响当前线程的任务执行.2018-02-23 11:39:07 GCD队列[2327:251270] 当前任务 2018-02-23 11:39:07 GCD队列[2327:251270] 下一个任务 2018-02-23 11:39:07 GCD队列[2327:251308] 自定义队列 0 <NSThread: 0x10203a210>{number = 4, name = (null)} 2018-02-23 11:39:07 GCD队列[2327:251308] 自定义队列 2 <NSThread: 0x10203a210>{number = 4, name = (null)} 2018-02-23 11:39:07 GCD队列[2327:251305] 自定义队列 1 <NSThread: 0x10203d9c0>{number = 5, name = (null)}
同步任务/异步任务/串行队列/并行队列(对比分析)
- 同步任务和异步任务
同步任务优先级高,在线程中有执行顺序,不会开启新的线程。 异步任务优先级低,在线程中执行没有顺序,看cpu闲不闲。在主队列中不会开启新的线程,其他队列会开启新的线程。
- 串行队列和并行队列
串行队列:队列中的任务按顺序执行(不会同时执行) 并行队列:队列中的任务会并发执行,可能会有一个疑问,队列不是先进先出吗,如果后面的任务执行完了,怎么出去的了。这里需要强调下,任务执行完毕了,不一定出队列。只有前面的任务执行完了,才会出队列。 串行队列里开启异步任务,是有顺序的 并行队列里开启同步任务是有执行顺序的,只有异步才没有顺序
- 在主队列中开启同步任务为什么会阻塞线程
在主队列开启同步任务,因为主队列是串行队列,里面的线程是有顺序的,先执行完一个线程才执行下一个线程, 而主队列始终就只有一个主线程,主线程是不会执行完毕的,因为他是无限循环的,除非关闭应用程序。 因此在主线程开启一个同步任务,同步任务会想抢占执行的资源,而主线程任务一直在执行某些操作,不肯放手。两个的优先级都很高,最终导致死锁,阻塞线程了
- 主线程队列中不能开启同步
主线程队列中不能开启同步,会阻塞主线程。只能开启异步任务,开启异步任务也不会开启新的线程,只是降低异步任务的优先级,让cpu空闲的时候才去调用。而同步任务,会抢占主线程的资源,会造成死锁。
- 为什么串行队列开启异步任务后嵌套同步任务造成死锁?
因为串行队列中线程是有执行顺序的,需要等上面开启的异步任务执行完毕,才会执行下面开启的同步任务。而上面的异步任务还没执行完,而下面的同步任务已经在抢占资源了,就会发生死锁。
- 串行队列中开启同步任务后嵌套同步任务造成死锁
因为串行队列中线程是有执行顺序的,需要等上面开启的同步任务执行完毕,才会执行下面开启的同步任务。而上面的同步任务还没执行完,而下面的同步任务已经在抢占资源了,就会发生死锁 串行队列开启同步任务后嵌套异步任务就不会造成死锁,开启异步,就会开启一个新的线程,不会阻塞线程
- 主线程队列和GCD创建的队列也是有区别?
主线程队列和GCD创建的队列是不同的。在GCD中创建的队列优先级没有主队列高,所以在GCD中的串行队列开启同步任务里面没有嵌套任务是不会阻塞主线程,只有一种可能导致死锁,就是串行队列里,嵌套开启任务,有可能会导致死锁。
- 队列
队列我们可以把它理解为是管理任务的,它里面放着很多的任务,来管理这些任务什么时候在哪些线程里面执行.队列是先进先出的