iOS 多线程-GCD任务+队列.
iOS 多线程-GCD队列组.
iOS 多线程-GCD栅栏方法.
多线程是开发中长用到的技术,特别是对于项目逻辑复杂的程序,更需要多线程的帮助。
在学习多线程之前需要来了解几个基本概念:
1、进程:每一个运行的程序都是一个进程。
2、线程:线程是进程中的一个执行单元,每一个进程中都至少有一个线程。
多线程就是指在进程中有多个线程处理任务,多线程的目的是为了提高CPU的利用率。
在iOS的开发中使用到的多线程技术有如下3种:
1、NSthread
2、GCD
3、NSOperation
GCD(Grand Central Dispatch)大中央调度,是apple开发的一个多核开发的解决办法。主要用于优化程序以支持多核处理器。
GCD的优点:
1、可用于多核的并行运算。
2、自动利用CPU更多的内核。
3、自动管理线程生命周期。
GCD之前先来了解多线程中的一些概念:
1、任务:需要执行的操作,程序中每一个需要线程执行的操作都是一个任务。
在多线程开发中对执行任务有两种方式:同步执行和异步执行,同步和异步的区别在于是否等待队列中的任务执行完毕和是否开辟新的线程
同步添加任务到指定队列,在添加任务执行结束之前,一直等待,直到队列中的任务执行完成再继续执行。
不开辟新的线程。
异步添加任务到指定队列中,不会出现等待,会继续执行。
开辟新的线程。
举个例子:
现在有A和B两个任务。
同步执行是的方式是先执行A,等A执行完成后再执行B。
异步执行是在执行A的时候也可以开始执行B,执行B的时候也可以执行A,两个任务不需要等待关系。
上面说到异步执行可以开辟新的线程,但是在实际的使用中是否开辟新的线程不是一定的,这个和添加到的队列有关。
队列是用来放置任务的,队列是一种特殊的线性表,采用FIFO(先进先出)的原则,及任务添加到队列是从队列尾部添加,而读取任务执行是从队列的头部开始读取。例如排队去食堂打饭,新来的人要排在队尾,先排队的先打饭。
GCD中有两种队列:串行队列和并发队列。
两种队列都是采用FIFO的原则,区别在于执行顺序不同和开辟线程数不同。
每次只有一个任务被执行,当被执行的任务执行完毕在执行下一个任务。队列中只有一个线程。
可以让多个任务同时执行,可以开辟多个线程来执行任务。
GCD是C语言开发的,在使用中也是使用C语言的调用方式。
GCD的使用方式很简单一共分为两步:
第一步:创建队列(串行队列、异步队列)
第二步:将任务添加到队列中,然后系统会根据任务类型执行任务(同步执行、异步执行)。
/**
GCD创建线程的方法:dispatch_queue_create()
这个方法中有两个参数:label和attr
label:线程队列的唯一标识,简单理解为队列名称,可以为空
attr:识别队列是串行队列还是并发队列,
DISPATCH_QUEUE_SERIAL: 串行队列
DISPATCH_QUEUE_CONCURRENT:并发队列
*/
//创建一个串行队列
dispatch_queue_t serialQueue = dispatch_queue_create("dispatchName", DISPATCH_QUEUE_SERIAL);
//创建一个并发队列
dispatch_queue_t concurrentQueue = dispatch_queue_create("dispatchName", DISPATCH_QUEUE_CONCURRENT);
在串行队列中GCD默认提供了一个主队列(Main Dispatch Queue)
主队列并不是特殊队列,实质上是一个普通的串行队列,只是默认情况下当前任务是放在主队列中执行。这个队列会将任务插入到主线程执行。
//GCD获取主队列的方式
dispatch_queue_t mainQueue = dispatch_get_main_queue();
在并发队列中GCD提供了一个全局并发队列(Global Dispatch Queue)
//GCD获取x全局并发队列
/**
获取全局并发队列的方法:dispatch_get_global_queue()
这个方法有两个参数:identifier和flags
identifier表示队列的优先级有下面4种:
DISPATCH_QUEUE_PRIORITY_HIGH 2 高优先级
DISPATCH_QUEUE_PRIORITY_DEFAULT 0 默认
DISPATCH_QUEUE_PRIORITY_LOW (-2) 低优先级
DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN 后台
flags是为了以后拓展用的,都写0就可以了
*/
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
任务的创建有两种方式(同步和异步),所以GCD提供了两种方式任务的创建方法:同步执行任务创建方法:dispatch_sync,异步执行任务创建方法:dispatch_async
//创建同步执行任务
/**
dispatch_sync()有一个参数queue和一个block
queue:表示需要将任务添加到的队列 本例中是将任务添加到了上面创建的一个串行队列中
block中是需要执行的任务
*/
dispatch_sync(serialQueue, ^{
//需要执行的任务
});
//创建异步执行任务
/**
dispatch_async()有一个参数queue和一个block
queue:表示需要将任务添加到的队列 本例中是将任务添加到了上面创建的一个并发队列中
block中是需要执行的任务
*/
dispatch_async(concurrentQueue, ^{
//需要执行的任务
});
到现在,一共出现了两个任务概念和两个队列概念。在开发中需要将两种任务执行方式和两种队列进行组合。简单来说就是我们需要先将任务的执行方式确定然后在添加到队列中。
在组合的过程中就会出现4中情况:
1、同步任务+串行队列
2、同步任务+并发队列
3、异步任务+串行队列
4、异步任务+并发队列
由于主队列是串行队列,全局并发队列是并发队列,这里不特殊列举了。最后在测试。
这4中组合到底会是什么样的执行顺序呢?带着这个疑问,后面来代码测试。
//1、创建一个串行队列,并添加同步执行任务
dispatch_queue_t queue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
//2、创建几个同步执行的任务
dispatch_sync(queue, ^{
//任务1
[NSThread sleepForTimeInterval:2]; //模拟耗时操作
NSLog(@"syncSerial1 ===== %@", [NSThread currentThread]); //打印当先任务和线程
});
dispatch_sync(queue, ^{
//任务2
[NSThread sleepForTimeInterval:2]; //模拟耗时操作
NSLog(@"syncSerial2 ===== %@", [NSThread currentThread]); //打印当先任务和线程
});
dispatch_sync(queue, ^{
//任务3
[NSThread sleepForTimeInterval:2]; //模拟耗时操作
NSLog(@"syncSerial3 ===== %@", [NSThread currentThread]); //打印当先任务和线程
});
dispatch_sync(queue, ^{
//任务4
[NSThread sleepForTimeInterval:2]; //模拟耗时操作
NSLog(@"syncSerial4 ===== %@", [NSThread currentThread]); //打印当先任务和线程
});
打印结果:
2020-03-18 14:48:39.072215+0800 GCDdemo[85268:1300199] syncSerial1 ===== {number = 1, name = main}
2020-03-18 14:48:41.073474+0800 GCDdemo[85268:1300199] syncSerial2 ===== {number = 1, name = main}
2020-03-18 14:48:43.074822+0800 GCDdemo[85268:1300199] syncSerial3 ===== {number = 1, name = main}
2020-03-18 14:48:45.075994+0800 GCDdemo[85268:1300199] syncSerial4 ===== {number = 1, name = main}
从上面的打印结果可以看到每一个任务执行的时间间隔2秒,也就是模拟耗时操作的2秒。任务是顺序执行的
//1、创建一个并发队列,并添加同步执行任务
dispatch_queue_t queue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_CONCURRENT);
//2、创建几个同步执行的任务
dispatch_sync(queue, ^{
//任务1
[NSThread sleepForTimeInterval:2]; //模拟耗时操作
NSLog(@"syncConcurrent1 ===== %@", [NSThread currentThread]); //打印当先任务和线程
});
dispatch_sync(queue, ^{
//任务2
[NSThread sleepForTimeInterval:2]; //模拟耗时操作
NSLog(@"syncConcurrent2 ===== %@", [NSThread currentThread]); //打印当先任务和线程
});
dispatch_sync(queue, ^{
//任务3
[NSThread sleepForTimeInterval:2]; //模拟耗时操作
NSLog(@"syncConcurrent3 ===== %@", [NSThread currentThread]); //打印当先任务和线程
});
dispatch_sync(queue, ^{
//任务4
[NSThread sleepForTimeInterval:2]; //模拟耗时操作
NSLog(@"syncConcurrent4 ===== %@", [NSThread currentThread]); //打印当先任务和线程
});
打印结果:
2020-03-18 14:50:00.318846+0800 GCDdemo[85314:1301623] syncConcurrent1 ===== {number = 1, name = main}
2020-03-18 14:50:02.319483+0800 GCDdemo[85314:1301623] syncConcurrent2 ===== {number = 1, name = main}
2020-03-18 14:50:04.321007+0800 GCDdemo[85314:1301623] syncConcurrent3 ===== {number = 1, name = main}
2020-03-18 14:50:06.322498+0800 GCDdemo[85314:1301623] syncConcurrent4 ===== {number = 1, name = main}
从上面的打印结果可以看到每一个任务执行的时间间隔2秒,也就是模拟耗时操作的2秒。任务是顺序执行的
//1、创建一个串行队列,并添加异步执行任务
dispatch_queue_t queue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
//2、创建几个异步步执行的任务
dispatch_async(queue, ^{
//任务1
[NSThread sleepForTimeInterval:2]; //模拟耗时操作
NSLog(@"asyncSerial 1 ===== %@", [NSThread currentThread]); //打印当先任务和线程
});
dispatch_async(queue, ^{
//任务2
[NSThread sleepForTimeInterval:2]; //模拟耗时操作
NSLog(@"asyncSerial 2 ===== %@", [NSThread currentThread]); //打印当先任务和线程
});
dispatch_async(queue, ^{
//任务3
[NSThread sleepForTimeInterval:2]; //模拟耗时操作
NSLog(@"asyncSerial 3 ===== %@", [NSThread currentThread]); //打印当先任务和线程
});
dispatch_async(queue, ^{
//任务4
[NSThread sleepForTimeInterval:2]; //模拟耗时操作
NSLog(@"asyncSerial 4 ===== %@", [NSThread currentThread]); //打印当先任务和线程
});
打印结果:
2020-03-18 14:53:45.095510+0800 GCDdemo[85430:1305376] asyncSerial 1 ===== {number = 4, name = (null)}
2020-03-18 14:53:47.097073+0800 GCDdemo[85430:1305376] asyncSerial 2 ===== {number = 4, name = (null)}
2020-03-18 14:53:49.101455+0800 GCDdemo[85430:1305376] asyncSerial 3 ===== {number = 4, name = (null)}
2020-03-18 14:53:51.106984+0800 GCDdemo[85430:1305376] asyncSerial 4 ===== {number = 4, name = (null)}
从上面的打印结果可以看到每一个任务执行的时间间隔2秒,也就是模拟耗时操作的2秒。任务是顺序执行的。而且这里的线程number都是4说明有一个新线程被开辟出来了。
//1、创建一个并发队列,并添加异步执行任务
dispatch_queue_t queue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_CONCURRENT);
//2、创建几个异步执行的任务
dispatch_async(queue, ^{
//任务1
[NSThread sleepForTimeInterval:2]; //模拟耗时操作
NSLog(@"asyncSerial 1 ===== %@", [NSThread currentThread]); //打印当先任务和线程
});
dispatch_async(queue, ^{
//任务2
[NSThread sleepForTimeInterval:2]; //模拟耗时操作
NSLog(@"asyncSerial 2 ===== %@", [NSThread currentThread]); //打印当先任务和线程
});
dispatch_async(queue, ^{
//任务3
[NSThread sleepForTimeInterval:2]; //模拟耗时操作
NSLog(@"asyncSerial 3 ===== %@", [NSThread currentThread]); //打印当先任务和线程
});
dispatch_async(queue, ^{
//任务4
[NSThread sleepForTimeInterval:2]; //模拟耗时操作
NSLog(@"asyncSerial 4 ===== %@", [NSThread currentThread]); //打印当先任务和线程
});
打印结果:
2020-03-18 14:58:47.534261+0800 GCDdemo[85593:1309727] asyncSerial 3 ===== {number = 5, name = (null)}
2020-03-18 14:58:47.534261+0800 GCDdemo[85593:1309728] asyncSerial 2 ===== {number = 8, name = (null)}
2020-03-18 14:58:47.534318+0800 GCDdemo[85593:1309732] asyncSerial 1 ===== {number = 7, name = (null)}
2020-03-18 14:58:47.534351+0800 GCDdemo[85593:1309729] asyncSerial 4 ===== {number = 4, name = (null)}
从上面的打印结果可以看到每一个任务执行结束的时间间隔不在是2秒,任务是并发执行的。而且每个人物的线程也都不一样,说明开辟了新的线程,每个线程执行一个任务,异步执行,几乎同时执行结束。
从上面4中组合情况来看,只有异步执行+并发队列才会出现开辟出多个线程来执行任务。