欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

iOS开发—使用GCD实现多线程

程序员文章站 2024-03-20 13:04:28
...

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分别为“串行同步任务”和“并行同步任务”。界面如下:

iOS开发—使用GCD实现多线程

(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
程序运行成功后,单击“串行同步任务”按钮,运行结果如下:

iOS开发—使用GCD实现多线程

从图中可以看出,任务都是在主线程中执行的,而且必须执行完上一个任务后,才会开始执行下一个任务。

单击“并行同步任务”按钮,运行结果如下:

iOS开发—使用GCD实现多线程

从图中可以看出,任务依然只在主线程中执行,而且是一个一个按顺序执行,这说明采用同步的方式不会开启新的线程。

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分别为“串行异步任务”和“并行异步任务”。界面如下:

iOS开发—使用GCD实现多线程

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
程序运行成功后,单击“串行异步任务”按钮,运行结果如下:

iOS开发—使用GCD实现多线程

从图中可以看出,线程的number值为2,说明创建了一个子线程。两个任务均是在该线程上被执行的,而且执行完成第1个任务之后才开始执行第2个任务。

单击“并行异步任务”按钮,运行结果如下图:

iOS开发—使用GCD实现多线程

从图中可以看出,这两个任务开启了两个不同的线程,而且任务完成的先后顺序是无法控制的,这表明两个线程是并发执行的,同时也证明了异步任务会开启新的线程。