iOS开发—使用GCD实现多线程
GCD是苹果公司为多核的并行运算提出的解决方案,工作时会自动利用更多的处理器核心。使用GCD,系统会完全管理线程,开发者无需编写线程代码。
GCD是Grand Central Dispatch 的缩写,它是基于C语言的。GCD会负责创建线程和调度学要执行的任务,由系统直接提供线程管理。GCD有两个核心概念:队列和任务。
1、队列
Dispatch Queue(队列),它是一个用来存放任务的集合,负责管理开发者提交的任务。队列的核心理念就是将长期运行的任务拆分成多个工作单元,并将这些单元添加到队列中,系统会代为管理这些队列,并放到多个线程上执行,无需开发者直接启动和管理后台线程。
系统提供了许多预定义的队列,包括可以保证始终在主线程上写执行工作的Dispatch Queue,也可以创建自定义的Dispatch Queue,而且可以创建任意多个。队列会维护和使用一个线程池来处理用户提交的任务,线程池的作用就是执行队列管理的任务。GCD的Dispatch Queue严格遵循FIFO(先进先出)原则,添加到Dispatch Queue的工作单元将始终按照加入Dispatch Queue的顺序启动。
需要注意的是:由于每个任务的执行时间各不相同,先处理的任务不一定先结束。
根据任务执行方式不同,队列主要分两种:
(1)Serial Dispatch Queue(串行队列)
串行队列底层的线程池只有一个线程,一次只能执行一个任务,前一个任务执行完成之后,才能够执行下一个任务。
(2)Concurrent Dispatch Queue(并发队列)
并行队列底层的线程池提供了多个线程,可以按照FIFO的顺序并发启动、执行多个任务,这样可以使应用程序的响应性能显著提高。
2、任务
任务就是用户提交给队列的工作单元,也就是代码块,这些任务会交给维护队列的线程池执行,因此这些任务会以多线程的方式执行。
综上,如果要使用GCD实现多线程,仅仅需要两个步骤:
(1)创建队列;
(2)将任务的代码块提交给队列。
创建队列
创建队列需要获取一个dispatch_queue_t类型的对象,iOS提供了多个创建或者访问队列的函数,大体分为3种情况:
1、获取全局并发队列(Global Concurrent Dispatch Queue)
全局并发队列可以同时并行的执行多个任务,但并发队列仍然按照先进先出的顺序来启动任务。并发队列会在前一个任务完成之前就启动下一个任务并开始执行,它同时执行的任务数量会根据应用和系统的动态变化,主要影响因素包括可用核数量、其他进程正在执行的工作数量、其他串行队列中优先任务的数量等。
系统会给每个应用提供多个并发的队列,整个应用内全局共享。开发者不需要显式地创建这些队列,只需要使用dispatch_get_global_queue()函数来获取这些队列,函数定义如下:
dispatch_queue_t dispatch_get_global_queue(long identifier,unsigned long flags);
该函数有两个参数,第2个参数是供以后使用的,传入0即可。第1个参数用于指定队列的优先级,包含4个宏定义的常量,定义格式如下:
#define DISPATCH_QUEUE_PRIORITY_HIGH 2 //高
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 //默认(中)
#define DISPATCH_QUEUE_PRIORITY_LOW (-2) //低
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN //后台
由下至上,这些值表示的优先级依次降低,分别表示高、中、低、后台,默认为种。以DISPATCH_QUEUE_PRIORITY_DEFAULT举例,获取系统默认的全局并发队列可以通过如下代码完成:
dispatch_queue_t queue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
2、创建串行和并行队列(Serial and Concurrent Dispatch Queue)
应用程序的任务如果要按照特定的顺序执行,需要使用串行队列,并且每次只能执行一项任务。尽管应用能够创任意数量的队列,但不要为了同时执行更多的任务二创建更多的队列。如果需要并发地执行大量的任务,应该把任务提交到全局并发队列。
开发者必须显式地创建和管理所有使用的串行队列,使用dispatch_queue_create()函数根据指定的字符串创建串行队列,函数定义如下所示:
dispatch_queue_t
dispatch_queue_create(const char *label,dispatch_queue_attr_t attr);
上述代码中,该函数有两个参数,第1个参数是用来表示队列的字符串,可以选择设置,也可以为NULL;第2个参数用于控制创建的是串行队列还是并发队列,若将参数设置为“DISPATCH_QUEUE_SERIAL”,则表示串行队列;若设置为"DISPATCH_QUEUE_CONCURRENT",则表示并发队列;若设置为NULL,则默认为串行队列。例如,label参数的值为“itcast.queue”,attr参数的值为NULL,创建串行队列可以通过如下代码完成:dispatch_queue_t queue=dispatch_queue_create("itcast.queue", NULL);
要注意的是,实际应用中,如果要使用并发队列,一般获取全局并发队列即可。
3、获取主队列(Main Queue)
主队列是GCD自带的一个特殊的串行队列,只要是提交给主队列的任务,就会放到主线程中执行。使用dispatch_get_main_queue()
函数可以获取主队列,函数定义如下:
dispatch_queue_t dispatch_get_main_queue(void);
上述代码中,函数只有一个返回值,而没有参数,获取主线程关联的队列可以通过如下代码完成:
dispatch_queue_t queue=dispatch_get_main_queue();
提交任务
队列创建完成后,需要将任务代码块提交给队列。若要向队列提交任务,可通过同步和异步两种方式实现。
1、以同步的方式执行任务
同步执行任务:只会在当前线程中执行任务,不具备开启新线程的能力。少数情况下,开发者可能希望同步地调用任务,避免竞争条件或者其他同步错误。通过dispatch_sync()和dispatch_sync_f()函数能够同步地添加任务到队列,这两个函数会阻塞当前调用线程,直到相应的任务完成执行,这两个函数的定义格式如下:
void dispatch_sync(dispatch_queue_t queue,^(void)block);
void dispatch_sync_f(dispatch_queue_t queue,void *context,dispatch_function_t work);
在上述定义格式中,这两个函数都没有返回值,而且第2个函数多一个参数,针对它们的介绍如下:
(1)dispatch_sync()函数:将代码块以同步的方式提交给指定队列,该队列底层的线程池将负责执行该代码块。其中,第1个参数表示任务将添加到的目标队列,第2个参数就是将要执行的代码块,也就是要执行的任务。
(2)dispatch_sync_f()函数:将函数以同步的方式提交给指定队列,该队列底层的线程池将负责执行该函数。其中,第1个参数与前面相同,第2个参数是向函数传入应用程序定义的上下文,第3个参数是要传入的其他需要执行的函数。
用一个简单的案例,展示如何使用同步方式向串行队列和并发队列提交任务,具体步骤如下:
(1)新建一个Single View Application应用,名称为04-Dispatch Syn;
(2)进入Main.StoryBoard,从对象库拖拽两个Button到程序界面,用于控制串行或者并行地执行同步任务,设置两个Button的Title分别为“串行同步任务”和“并行同步任务”。界面如下:
(3)采用拖拽的方式,为“串行同步任务”按钮和“并行同步任务”按钮添加两个单击响应事件,分别命名为synSerial:和SynConcurrent:。进入ViewController.m实现这两个响应按钮单击的方法,代码如下:
#import "ViewController.h"
@interface ViewController ()
- (IBAction)synSerial:(id)sender;
- (IBAction)synConcurrent:(id)sender;
@end
@implementation ViewController
dispatch_queue_t serialQueue;
dispatch_queue_t globalQueue;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//创建串行队列
serialQueue=dispatch_queue_create("cn.itcast", DISPATCH_QUEUE_SERIAL);
//获取全局并发队列
globalQueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
dispatch_queue_t dispatch_get_global_queue(long identifier,unsigned long flags);
//单击“串行同步任务”后执行的行为
- (IBAction)synSerial:(id)sender {
dispatch_sync(serialQueue,^{
for(int i=0;i<100;i++){
NSLog(@"%@---task1---%d",[NSThread currentThread],i);
}
});
dispatch_sync(serialQueue,^{
for(int i=0;i<100;i++){
NSLog(@"%@---task2---%d",[NSThread currentThread],i);
}
});
}
//单击“并行同步任务”后执行的行为
- (IBAction)synConcurrent:(id)sender {
dispatch_sync(globalQueue,^{
for(int i=0;i<100;i++){
NSLog(@"%@---task1---%d",[NSThread currentThread],i);
}
});
dispatch_sync(globalQueue,^{
for(int i=0;i<100;i++){
NSLog(@"%@---task2---%d",[NSThread currentThread],i);
}
});
}
@end
程序运行成功后,单击“串行同步任务”按钮,运行结果如下:
从图中可以看出,任务都是在主线程中执行的,而且必须执行完上一个任务后,才会开始执行下一个任务。
单击“并行同步任务”按钮,运行结果如下:
从图中可以看出,任务依然只在主线程中执行,而且是一个一个按顺序执行,这说明采用同步的方式不会开启新的线程。
2、以异步的方式执行任务
异步执行任务:会在新的线程中执行任务,具备开启新线程的能力。当开发者添加一些任务到队列中时,无法确定这些代码什么时候能够执行。通过异步地添加代码块或函数,可以让线程池立即执行这些代码,然后还可以调用线程继续去做其他事情。开发者应该尽可能地使用dispatch_async()或dispatch_async_f()函数异步地调度任务,这两个函数如下:
void dispatch_async(dispatch_queue_t queue,^(void)block);
void dispatch_async_f(dispatch_queue_t queue,void *context,dispatch_function_t,work);
从上述代码看出,这两个函数都没有返回值,具体传入的参数和同步函数的参数一样,对它们的介绍如下:
(1)dispatch_async()函数:将代码块以异步的方式提交给指定队列,该队列底层的线程池将负责执行该代码块。
(2)dispatch_async_f()函数:将函数以异步的方式提交给指定队列,该队列底层的线程池将负责执行该函数。
需要注意的是,应用程序的主线程一定要异步地调度任务,这样才能及时地响应用户事件。
通过一个简单的案例,展示如何使用异步的方式向串行队列和并发队列提交任务,具体步骤:
(1)新建一个Single View Application应用,名称为05-Dispatch Asyn;
(2)进入Main.StoryBoard,从对象库拖拽两个Button到程序界面,用于控制串行或者并行地执行异步任务,设置两个Button的Title分别为“串行异步任务”和“并行异步任务”。界面如下:
3)采用拖拽的方式,为“串行异步任务”按钮和“并行异步任务”按钮添加两个单击响应事件,分别命名为asynSerial:和asynConcurrent:。进入ViewController.m实现这两个响应按钮单击的方法,代码如下:
#import "ViewController.h"
@interface ViewController ()
- (IBAction)asynSerial:(id)sender;
- (IBAction)asynConcurrent:(id)sender;
@end
@implementation ViewController
dispatch_queue_t serialQueue;
dispatch_queue_t concurrentQueue;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//创建串行队列
serialQueue = dispatch_queue_create("cn.itcast", DISPATCH_QUEUE_SERIAL);
//创建并发队列
concurrentQueue = dispatch_queue_create("cn.itcast", DISPATCH_QUEUE_CONCURRENT);
}
//单击“串行异步任务”按钮后执行的行为
- (IBAction)asynSerial:(id)sender {
dispatch_async(serialQueue,^{
for(int i=0;i<100;i++){
NSLog(@"%@---task1---%d",[NSThread currentThread],i);
}
});
dispatch_async(serialQueue,^{
for(int i=0;i<100;i++){
NSLog(@"%@---task2---%d",[NSThread currentThread],i);
}
});
}
//单击“并行异步任务”按钮后执行的行为
- (IBAction)asynConcurrent:(id)sender {
dispatch_async(concurrentQueue,^{
for(int i=0;i<100;i++){
NSLog(@"%@---task1---%d",[NSThread currentThread],i);
}
});
dispatch_async(concurrentQueue,^{
for(int i=0;i<100;i++){
NSLog(@"%@---task2---%d",[NSThread currentThread],i);
}
});
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
程序运行成功后,单击“串行异步任务”按钮,运行结果如下:
从图中可以看出,线程的number值为2,说明创建了一个子线程。两个任务均是在该线程上被执行的,而且执行完成第1个任务之后才开始执行第2个任务。
单击“并行异步任务”按钮,运行结果如下图:
从图中可以看出,这两个任务开启了两个不同的线程,而且任务完成的先后顺序是无法控制的,这表明两个线程是并发执行的,同时也证明了异步任务会开启新的线程。
上一篇: PHP 之session cookie
下一篇: 数字在排序数组中出现的次数
推荐阅读
-
iOS开发—使用GCD实现多线程
-
使用jquery及javascript实现文本滚动效果 博客分类: web开发学习笔记 JavaScriptjQuery
-
CocoaPods使用 博客分类: 常用命令iOS开发
-
Github使用https协议进行下载 博客分类: iOS开发Web前端
-
CocoaPods使用 博客分类: 常用命令iOS开发
-
Github使用https协议进行下载 博客分类: iOS开发Web前端
-
使用UIWindow实现iOS应用屏保功能
-
ios开发中ActionSheetPicker实现全国省市县三级联动选择器 博客分类: 前端
-
使用java的HttpClient实现多线程并发
-
JavaWeb开发之使用jQuery与Ajax实现动态联级菜单效果