1. 并行和并发有什么区别?
并行:同一时刻同时执行不同的任务, 关键点在于同时, 当然想达到这一点至少有两条线程;
并发:同一时间段可以执行不同的任务, 关键在于 可以执行不同的任务, 想做到这一点 一条线程也可以,因为可以在一条线程中交替执行, 多条线程也可以达到, 不同的线程中执行不同的任务;
2. 线程和进程?
进程:具有独立功能的关于某个数据集合上的第一次运行活动,是系统资源分配和调度的独立单位;
线程:是进程的一个实体,是CPU调度和分派的基本代为,它与同一进程中的其他线程共享该进程中所有资源;
区别:
1,一个程序至少一个进程,一个进程至少有一个线程;
2, 进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率
3, 线程的划分尺度小于进程,使得多线程程序的并发性高进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了,同一个进程中的多个线程之间可以并发执行
收拾下心情,进入我们的主角GCD:
1、为什么要用GCD?
- GCD会自动帮我们管理线程的生命周期,它内部维护着一个线程池;
- 它可以更好的利用多核CPU;
- 使用起来方便,我们只需要将需要执行的任务放入block就可以了;
2、如何更好的理解任务和队列;
任务:你让程序干的事情,也就是程序需要执行哪些操作;GCD中通常block里面就是一系列的任务;]
![](https://img.haomeiwen.com/i2099412/b4f67a627960d360.png)
队列:队列是用来装任务的,管理任务的,不然任务就乱套了; 它采用FIFO先进先出的原则,不管是串行队列还是并发队列都是一样的,注意这里仅仅指的是出队列的顺序!!!
![](https://img.haomeiwen.com/i2099412/04fd43e0019e8980.png)
如图:任务4、3、2、1依次放入队列中,那么任务出去的顺序是4、3、2、1;
串行队列: 队列中的任务只能一个个执行,前面一个没有执行完,后面一个不能执行;
![](https://img.haomeiwen.com/i2099412/1283d61919c64785.png)
并发队列: 前面一个任务出队列开始执行的时候,后面一个就 可以 在新的线程去执行了,注意这里是可以,表示一种能力,不一定会这样;
![](https://img.haomeiwen.com/i2099412/25c7ed7be19d2d6e.png)
后面一个任务看到前面一个在开始执行状态就可以出队列,准备开始执行了
3、同步和异步
同步:
- 不开启新的线程就在当前线程执行,
- 必须等block里面的代码执行完,dispathch才会返回;执行后面的,会阻塞当前线程;
异步:
- 可以开启新的线程,注意是可以 并不一定会,它有这个能力;
- 不需要等block里面的代码执行完,立即返回,不会阻塞当前线程;
4、一个关于GCD的故事(本故事纯属本人虚构,如果有不恰当的地方待未来改正)
有两个咖啡店,咖啡店1只有一个窗口卖, 咖啡店2有很多窗口卖, 然后两个老师都带着 排着一条长队的 同学去买咖啡, 其中一个老师A告诉她的学生,你们等前面一个买好再接着买,另一个老师B告诉她的学生,你前面的同学开始买选咖啡的时候,下一个就可以去找其他窗口开始买了,不要等的;
- A老师把她的同学带到了咖啡店1:
学生们是只能等前面买好才能买的,而且就只有一个窗口, 所以结果:一个接一个按顺序买就好
-
A老师把她的同学带到了咖啡店2:
前面一个同学正在买的时候,后面一个想去找其他窗口开始买,但是现实告诉他没有其他窗口了,老老实实等前面一个买好吧,,,,,所以结果:一个接一个按顺序买就好 -
B老师把她的同学带到了咖啡店1:
虽然现实很好,有好多窗口卖咖啡,但是 学生都是乖孩子,有纪律的,前面一个买好后面才能买,所以就只需要去其他窗口中的一个窗口买就好了,结果:一个接一个按顺序买就好 -
B老师把她的同学带到了咖啡店2:
这里的孩纸是最幸运的,天时地利人和; 学生可以找其他窗口买,而且这里恰好就有好多窗口,他们几乎同时的在不同的窗户买起咖啡来; 结果:同时买;
5、GCD中的六种组合方式
一. 串行队列 + 同步执行
- (void) GCDTest1
{
dispatch_queue_t queue = dispatch_queue_create("serialQueue.xj.com", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
NSLog(@"1------>>>%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"2------>>>%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"3------>>>%@",[NSThread currentThread]);
});
NSLog(@"4------>>>%@",[NSThread currentThread]);
}
结果:
2018-04-01 10:16:17.186574+0800 GCD[55764:12462142] 1------>>><NSThread: 0x604000261c00>{number = 1, name = main}
2018-04-01 10:16:17.186850+0800 GCD[55764:12462142] 2------>>><NSThread: 0x604000261c00>{number = 1, name = main}
2018-04-01 10:16:17.187063+0800 GCD[55764:12462142] 3------>>><NSThread: 0x604000261c00>{number = 1, name = main}
2018-04-01 10:16:17.187228+0800 GCD[55764:12462142] 4------>>><NSThread: 0x604000261c00>{number = 1, name = main}
分析:
这里对应上面故事的第一种情况,由于是同步,不开线程,当前线程为主线程,所以所有任务都在主线程中执行,又因为是串行队列,任务按顺序依次执行,而且还是同步,每次需等block内执行完,所以顺序是1-->2--->3--->4;
二. 串行队列 + 异步执行
- (void) GCDTest2
{
dispatch_queue_t queue = dispatch_queue_create("serialQueue.xj.com", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"1------>>>%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"2------>>>%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"3------>>>%@",[NSThread currentThread]);
});
NSLog(@"4------>>>%@",[NSThread currentThread]);
}
结果:
2018-04-01 10:22:41.780449+0800 GCD[56160:12471344] 4------>>><NSThread: 0x604000079940>{number = 1, name = main}
2018-04-01 10:22:41.780478+0800 GCD[56160:12471596] 1------>>><NSThread: 0x60400027f0c0>{number = 3, name = (null)}
2018-04-01 10:22:41.780803+0800 GCD[56160:12471596] 2------>>><NSThread: 0x60400027f0c0>{number = 3, name = (null)}
2018-04-01 10:22:41.781027+0800 GCD[56160:12471596] 3------>>><NSThread: 0x60400027f0c0>{number = 3, name = (null)}
分析:
因为是异步执行,不会阻塞当前线程,所以先执行4,,开辟了一条新的线程执行1,2,3,然而1,2,3是放到串行队列中的,所以依次然顺序执行1-->2-->3; 这里提一下,既然是异步,为什么不开辟多条线程分别执行1,2,3,没这个必要,既然已经开了一天线程,后面的任务要等前面的执行完再执行,这个线程足以让1,2,3执行任务了,开辟线程是消耗资源的;
三. 并发队列 + 同步执行
- (void) GCDTest3
{
dispatch_queue_t queue = dispatch_queue_create("xj", DISPATCH_QUEUE_CONCURRENT);;
dispatch_sync(queue, ^{
NSLog(@"1------>>>%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"2------>>>%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"3------>>>%@",[NSThread currentThread]);
});
NSLog(@"4------>>>%@",[NSThread currentThread]);
}
结果
2018-04-01 10:30:24.402362+0800 GCD[56597:12479909] 1------>>><NSThread: 0x60400006f580>{number = 1, name = main}
2018-04-01 10:30:24.432775+0800 GCD[56597:12479909] 2------>>><NSThread: 0x60400006f580>{number = 1, name = main}
2018-04-01 10:30:24.439568+0800 GCD[56597:12479909] 3------>>><NSThread: 0x60400006f580>{number = 1, name = main}
2018-04-01 10:30:24.439568+0800 GCD[56160:12471344] 4------>>><NSThread: 0x604000079940>{number = 1, name = main}
分析:由于是同步执行,所以不会另开线程,阻塞当前线程,所以4是最后执行的,虽然是并发队列可以不用等前面的任务执行完,但是就一条线程,不得不等前面的任务执行完才执行后面的任务,所以1,2,3依次执行;
四. 并发队列 + 异步执行
- (void) GCDTest4
{
dispatch_queue_t queue = dispatch_queue_create("xj", DISPATCH_QUEUE_CONCURRENT);;
dispatch_async(queue, ^{
NSLog(@"1------>>>%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"2------>>>%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"3------>>>%@",[NSThread currentThread]);
});
NSLog(@"4------>>>%@",[NSThread currentThread]);
}
结果:
2018-04-01 10:31:14.552817+0800 GCD[56662:12481282] 4------>>><NSThread: 0x604000069640>{number = 1, name = main}
2018-04-01 10:31:14.552818+0800 GCD[56662:12481369] 2------>>><NSThread: 0x6000002742c0>{number = 4, name = (null)}
2018-04-01 10:31:14.552820+0800 GCD[56662:12481372] 3------>>><NSThread: 0x600000274240>{number = 5, name = (null)}
2018-04-01 10:31:14.552838+0800 GCD[56662:12481370] 1------>>><NSThread: 0x604000268a40>{number = 3, name = (null)}
分析:
这是最常用的一种方式,因为它是异步队列,所以它是不会堵塞当前线程的,4会先执行,由于是并发队列,前面的任务开始执行了,后面的任务便可以出队列,异步执行导致为它单独开辟线程执行,所以1,2,3会在不同的线程中执行,如果是1,2,3分别是耗时的操作,那么它们的执行顺序将不得而知,随机;
五. 主队列 + 同步执行
-
当前线程为主线程:
- (void)viewDidLoad { [super viewDidLoad]; NSLog(@"1------>>>%@",[NSThread currentThread]); dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"2------>>>%@",[NSThread currentThread]); }); NSLog(@"3------>>>%@",[NSThread currentThread]);
结果:打印了1---->>>main后崩溃;
分析:
很多博客中把这个案例都说的很好,这里我讲下我的理解,首先viewDidLoad本身就是主线程中执行的,被放在了主队列中,当执行它的时候发现,里面有个同步,必须等block里面的任务执行完才行,也就是必须执行2,但是这时候,2又被放入了主队列,主队列是串行的,很显然,在主队列中2是排在viewDidload任务后面的,那么它必须等viewDidload执行完才可以去执行,然后viewDidload必须等2执行完,才算完,,,,这样陷入死循环,就崩溃了;
- 当前线程为分线程:
- (void) GCDTest5
{
NSLog(@"1------>>>%@",[NSThread currentThread]);
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2------>>>%@",[NSThread currentThread]);
});
NSLog(@"3------>>>%@",[NSThread currentThread]);
}
结果:
2018-04-01 10:41:49.864270+0800 GCD[57307:12495333] 1------>>><NSThread: 0x604000664d80>{number = 3, name = (null)}
2018-04-01 10:41:49.868650+0800 GCD[57307:12495215] 2------>>><NSThread: 0x604000261d00>{number = 1, name = main}
2018-04-01 10:41:49.869738+0800 GCD[57307:12495333] 3------>>><NSThread: 0x604000664d80>{number = 3, name = (null)}
分析:
这里不同于上面的点在于,GCDTest5被放在了分线程,那么就好办了,分线程中执行GCDTest5任务时遇到同步,就会一直等,2被放到了主队列,那么2会在所有它前面的主队列中的任务执行完后执行,执行完后分线程中GCDTest5会继续往后执行;
六. 主队列 + 异步执行
- (void) GCDTest6
{
NSLog(@"1------>>>%@",[NSThread currentThread]);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"2------>>>%@",[NSThread currentThread]);
});
NSLog(@"3------>>>%@",[NSThread currentThread]);
}
结果:
2018-04-01 10:46:06.757861+0800 GCD[57573:12500988] 1------>>><NSThread: 0x60000007f500>{number = 1, name = main}
2018-04-01 10:46:06.758154+0800 GCD[57573:12500988] 3------>>><NSThread: 0x60000007f500>{number = 1, name = main}
2018-04-01 10:46:06.764103+0800 GCD[57573:12500988] 2------>>><NSThread: 0x60000007f500>{number = 1, name = main}
分析:
这个没什么悬念,主队列中的任务是在主线程中执行的,而且主队列是串行队列;
6、线程中的通信方式
7、使用GCD需要注意的地方
8、GCD中的其他重要函数
-
队列组
当有多个异步任务的时候,但是我们需要这些任务都完成的时候,才执行一些操作,由于是异步,我们无法保证所有任务的结果,这时候就可以用到dispatch_group, 比如说我们一个首页界面有好多请求,但是我想等所有请求都有结果的时候再显示,不然显示不全会很难看,这时候我们可以这样:
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 3; ++i) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
}
});
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
}
});
dispatch_group_notify(group, queue, ^{
NSLog(@"---所有任务完成了--->>");
});
网友评论