iOS多线程之--GCD详解



iOS多线程demohtml

iOS多线程之--NSThreadios

iOS多线程之--GCD详解git

iOS多线程之--NSOperationgithub

iOS多线程之--线程安全(线程锁)面试

iOS多线程相关面试题swift



1. 什么是 GCD

GCD(Grand Central Dispatch)是苹果公司为多核的并行运算提出的解决方案,它是一套纯C语言实现的API。使用GCD分派队列(dispath queue)能够同步或者异步地执行任务,以及串行或者并发的执行任务。GCD会自动管理线程的生命周期(建立线程、调度任务、销毁线程),因此开发人员只须要告诉GCD想要执行什么任务,而不须要编写任何管理线程的代码。[GCD源码]安全

2. 相关名词解释

2.1 同步和异步

同步和异步主要体如今能不能开启新的线程,以及会不会阻塞当前线程。bash

  • 同步:在当前线程中执行任务,不具有开启新线程的能力。同步任务会阻塞当前线程。
  • 异步:在新的线程中执行任务,具有开启新线程的能力。并不必定会开启新线程,好比在主队列中经过异步执行任务并不会开启新线程。异步任务不会阻塞当前线程。

2.2 串行和并发

串行和并发主要影响任务的执行方式。服务器

  • 串行:一个任务执行完毕后,再执行下一个任务。
  • 并发:多个任务并发(同时)执行。

注意:若是当前队列是串行队列,经过同步函数向当前队列中添加任务会形成死锁。(串行队列中添加异步任务和并发队列中添加同步任务都不会死锁。)网络

3. GCD如何执行任务

任务就是要执行什么操做,GCD中任务是放在block中(dispatch_block_t)或者函数中(dispatch_function_t)的(比较常见的是放在block中)。任务的执行方式有同步异步2种。GCD中的同步函数和异步函数以下:

//同步执行
void dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);
void dispatch_sync_f(dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
void dispatch_barrier_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);
void dispatch_barrier_sync_f(dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);

//异步执行
void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
void dispatch_async_f(dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
void dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);
void dispatch_group_async_f(dispatch_group_t group, dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
void dispatch_barrier_async_f(dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
复制代码

这里咱们先经过dispatch_asyncdispatch_async_f这两个异步函数来了解block任务和函数任务的使用方法,其它函数后面会单独拿出来介绍。这里要说明的是,block任务和函数任务功能是同样的,因此后面内容都以block任务来作讲解

3.1 block任务(dispatch_block_t)的使用方法

void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

  • 参数一(queue):派发队列,将任务添加到这个队列中。
  • 参数二(block):执行任务的block。
- (void)blockTask{
    NSLog(@"%@",[NSThread currentThread]);
    dispatch_async(dispatch_get_global_queue(0, 0), ^{ // blcok任务
        [NSThread sleepForTimeInterval:1.0f]; // 模拟耗时任务
        NSLog(@"block任务--%@",[NSThread currentThread]);
    });
}

// ****************运行结果****************
2019-12-28 18:58:08.194661+0800 MultithreadingDemo[21777:4006228] <NSThread: 0x60000124a200>{number = 1, name = main}
2019-12-28 18:58:09.205977+0800 MultithreadingDemo[21777:4006340] block任务--<NSThread: 0x600001214bc0>{number = 3, name = (null)}
复制代码

3.2 函数任务(dispatch_function_t)的使用方法

void dispatch_async_f(dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);

  • 参数一(queue):派发队列,将任务添加到这个队列中。
  • 参数三(work):执行任务的函数,dispatch_function_t是一个函数指针(其定义为:typedef void (*dispatch_function_t)(void *_Nullable);),指向耗时任务所在的的函数。
  • 参数二(context):是给耗时任务的函数传的参数,参数类型是任意类型。
- (void)functionTask{
    NSLog(@"%@",[NSThread currentThread]);
    dispatch_async_f(dispatch_get_global_queue(0, 0), @"abc124", testFunction);
}

// 任务所在的函数
void testFunction(void *para){
    [NSThread sleepForTimeInterval:1.0f]; // 模拟耗时任务
    NSLog(@"函数任务参数:%@----线程:%@",para,[NSThread currentThread]);
}

// ****************运行结果****************
2019-12-28 18:51:01.711140+0800 MultithreadingDemo[21777:4006228] <NSThread: 0x60000124a200>{number = 1, name = main}
2019-12-28 18:51:02.716100+0800 MultithreadingDemo[21777:4006340] 函数任务参数:abc124----线程:<NSThread: 0x600001214bc0>{number = 3, name = (null)}
复制代码

4. GCD的队列(dispatch_queue)

GCD的派发队列(dispatch_queue)用来存听任务(dispatch_block)并控制任务的执行方式。(dispatch_queue)采用的是FIFO(先进先出)的原则,也就是说先提交的任务会先安排执行(先安排并不意味着先执行完)。

4.1 队列的类型

GCD的队列能够分为2大类型:串行队列并发队列

  • 串行队列(Serial Dispatch Queue):让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)。
  • 并发队列(Concurrent Dispatch Queue):可让多个任务并发(同时)执行(自动开启多个线程同时执行任务)。要注意的是并发功能只有在异步(dispatch_async)函数下才有效。并发数是有上限的,并发执行的任务数量取决于当前系统的状态。

4.2 如何获取队列

4.2.1 主队列

主队列由系统自动建立,并与应用程序的主线程相关联。主队列上的任务必定是在主线程上执行(不过主线程并非只执行主队列的任务,为了不线程切换对性能的消耗,主线程还有可能会执行其余队列的任务)。

主队列是一个串行队列,因此即使是在异步函数中也不会去开启新的线程。它只有在主线程空闲的时候才能调度里面的任务。

开发中通常是子线程执行完耗时操做后,咱们获取到主队列并将刷新UI的任务添加到主队列中去执行。

// 主队列同步函数
- (void)mainQueueTest1{
    NSLog(@"主队列同步函数");
    // 获取主队列
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    
    for (NSInteger i = 0; i < 5; i++) {
        dispatch_sync(mainQueue, ^{
            [NSThread sleepForTimeInterval:1.0f]; // 模拟耗时操做
            NSLog(@"%ld--%@",i,[NSThread currentThread]);
        });
    }
}

// ****************运行结果****************
------线程堵塞------
复制代码

上面程序运行会堵塞线程,由于主队列是串行队列,若是经过同步函数向主队列中添加任务,那么并不会开启子线程来执行这个任务,而是在主线程中执行。咱们将方法mainQueueTest1称做task1,将同步函数添加的任务称做task2。task1和task2都在主队列中,主队列先安排task1到主线程执行,等task1执行了完了才能安排task2到主线程执行,而task1又必须等task2执行完了才能继续往下执行,而task2又必须等task1执行完了才能被主队列安排执行,这样就形成了相互等待而卡死(死锁)。

因此只要当前队列是串行队列,经过同步函数往当前队列添加任务都会形成死锁。


// 主队列异步函数
- (void)mainQueueTest2{
    NSLog(@"主队列异步函数");
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    
    for (NSInteger i = 0; i < 5; i++) {
        dispatch_async(mainQueue, ^{
            [NSThread sleepForTimeInterval:1.0f]; // 模拟耗时操做
            NSLog(@"%ld--%@",i,[NSThread currentThread]);
        });
    }
    NSLog(@"----end----");
}

// ****************运行结果****************
2019-12-28 15:42:04.622806+0800 MultithreadingDemo[20363:3934000] 主队列异步函数
2019-12-28 15:42:04.623171+0800 MultithreadingDemo[20363:3934000] ----end----
2019-12-28 15:42:05.625086+0800 MultithreadingDemo[20363:3934000] 0--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:42:06.625727+0800 MultithreadingDemo[20363:3934000] 1--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:42:07.627400+0800 MultithreadingDemo[20363:3934000] 2--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:42:08.628809+0800 MultithreadingDemo[20363:3934000] 3--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:42:09.629977+0800 MultithreadingDemo[20363:3934000] 4--<NSThread: 0x60000300d040>{number = 1, name = main}
复制代码

在主队列中经过异步函数添加的任务都是在主线程执行(由于主队列中的任务都在主线程执行,因此并不会开启新的线程),而且是串行执行。


4.2.2 获取全局队列

获取全局队列的函数是dispatch_get_global_queue(long identifier, unsigned long flags)。第一个参数表示队列的优先级(优先级等级以下表所示);第二个参数是保留参数,传0便可。全局队列是一个并发队列

队列优先级
优先级 描述
DISPATCH_QUEUE_PRIORITY_HIGH 最高优先级
DISPATCH_QUEUE_PRIORITY_DEFAULT 默认优先级
DISPATCH_QUEUE_PRIORITY_LOW 低优先级
DISPATCH_QUEUE_PRIORITY_BACKGROUND 后台优先级(表示用户不须要知道任务何时完成,选这项速度会很是慢)

注意这里的优先级和NSOperation中的优先级不一样,这里的优先级是指队列的优先级,NSOperation中的优先级是指任务的优先级。

对于全局队列,若是两个参数同样,那么获取的是同一个队列,以下所示queue1和queue2是同一个队列

// 打印两个队列的地址发现是同一个队列
dispatch_queue_t queue1 = dispatch_get_global_queue(0, 0);
dispatch_queue_t queue2 = dispatch_get_global_queue(0, 0);
复制代码

// 全局队列同步函数
- (void)globalQueueTest1{
    NSLog(@"全局队列同步");
    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    for (NSInteger i = 0; i < 5; i++) {
        dispatch_sync(globalQueue, ^{
            [NSThread sleepForTimeInterval:1.0f]; // 模拟耗时操做
            NSLog(@"%ld--%@",i,[NSThread currentThread]);
        });
    }
    NSLog(@"----end----");
}

// ****************打印结果****************
2019-12-28 15:42:41.301152+0800 MultithreadingDemo[20363:3934000] 全局队列同步
2019-12-28 15:42:42.302694+0800 MultithreadingDemo[20363:3934000] 0--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:42:43.304170+0800 MultithreadingDemo[20363:3934000] 1--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:42:44.305796+0800 MultithreadingDemo[20363:3934000] 2--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:42:45.307420+0800 MultithreadingDemo[20363:3934000] 3--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:42:46.309029+0800 MultithreadingDemo[20363:3934000] 4--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:42:46.309392+0800 MultithreadingDemo[20363:3934000] ----end----
复制代码

可见经过同步函数向全局队列添加的任务都是在当前线程执行(本例中当前线程是主线程),而且是串行执行。


// 全局队列异步函数
- (void)globalQueueTest2{
    NSLog(@"全局队列异步");
    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    for (NSInteger i = 0; i < 5; i++) {
        dispatch_async(globalQueue, ^{
            [NSThread sleepForTimeInterval:1.0f]; // 模拟耗时操做
            NSLog(@"%ld--%@",i,[NSThread currentThread]);
        });
    }
    NSLog(@"----end----");
}

// ****************打印结果****************
2019-12-28 15:43:44.399828+0800 MultithreadingDemo[20363:3934000] 全局队列异步
2019-12-28 15:43:44.400040+0800 MultithreadingDemo[20363:3934000] ----end----
2019-12-28 15:43:45.405629+0800 MultithreadingDemo[20363:3936377] 0--<NSThread: 0x600003065740>{number = 5, name = (null)}
2019-12-28 15:43:45.405655+0800 MultithreadingDemo[20363:3936378] 2--<NSThread: 0x60000306b100>{number = 9, name = (null)}
2019-12-28 15:43:45.405722+0800 MultithreadingDemo[20363:3934072] 1--<NSThread: 0x60000306b080>{number = 6, name = (null)}
2019-12-28 15:43:45.405657+0800 MultithreadingDemo[20363:3936380] 4--<NSThread: 0x60000306cb40>{number = 7, name = (null)}
2019-12-28 15:43:45.405779+0800 MultithreadingDemo[20363:3936379] 3--<NSThread: 0x600003061780>{number = 8, name = (null)}
复制代码

可见经过异步函数向全局队列添加的任务不是在当前线程执行,而是多个任务是在不一样的线程中执行,而且是并发执行。


4.2.3 本身建立队列

本身建立队列的方法以下,能够建立串行队列并发队列两种队列。

  • DISPATCH_QUEUE_SERIAL:串行队列,队列中的任务按先进先出的顺序连续执行(一个任务执行完了才能执行下一个任务)。
  • DISPATCH_QUEUE_SERIAL_INACTIVE:串行队列,此时这个串行队列的状态是不活跃的,在这个串行队列调用以前,必须使用dispatch_activate()函数激活队列(仅支持ios 10.0及之后的系统)。
  • DISPATCH_QUEUE_CONCURRENT:并发队列,队列中的任务能够并发(同时)执行。
  • DISPATCH_QUEUE_CONCURRENT_INACTIVE:并发队列,此时这个并发队列的状态是不活跃的,在这个并发队列调用以前,必须使用dispatch_activate()函数激活队列(仅支持ios 10.0及之后的系统)。
/**
    参数1:队列的名称,方便调试
    参数2:队列的类型,
    若是是串行队列,参数2为DISPATCH_QUEUE_SERIAL或者NULL
    若是是并发队列,参数2为DISPATCH_QUEUE_CONCURRENT
*/
dispatch_queue_create(const char *_Nullable label,
		dispatch_queue_attr_t _Nullable attr);
复制代码

对于本身建立的队列,若是两个参数同样,那么建立的是两个不一样的队列,以下所示queue1和queue2是不一样的队列

// 打印两个队列的地址发现是不一样的队列
dispatch_queue_t queue1 = dispatch_queue_create("abc", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("abc", DISPATCH_QUEUE_SERIAL);
复制代码

// 自建并串行列同步函数
- (void)customQueueTest1{
    NSLog(@"自建串行队列同步函数");
    dispatch_queue_t serialQueue = dispatch_queue_create("com.test.myQueue", DISPATCH_QUEUE_SERIAL);
    
    for (NSInteger i = 0; i < 5; i++) {
        dispatch_sync(serialQueue, ^{
            [NSThread sleepForTimeInterval:1.0f]; // 模拟耗时操做
            NSLog(@"%ld--%@",i,[NSThread currentThread]);
        });
    }
    NSLog(@"----end----");
}


// ****************打印结果****************
2019-12-28 15:45:26.206409+0800 MultithreadingDemo[20363:3934000] 自建串行队列同步函数
2019-12-28 15:45:27.207582+0800 MultithreadingDemo[20363:3934000] 0--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:45:28.208068+0800 MultithreadingDemo[20363:3934000] 1--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:45:29.208996+0800 MultithreadingDemo[20363:3934000] 2--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:45:30.210593+0800 MultithreadingDemo[20363:3934000] 3--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:45:31.211514+0800 MultithreadingDemo[20363:3934000] 4--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:45:31.211855+0800 MultithreadingDemo[20363:3934000] ----end----
复制代码

可见经过同步函数向本身建立的串行队列中添加的任务都是在当前线程执行,不会开启新的线程,并且是串行执行。

注意这个和经过同步函数向主队列添加任务不一样。咱们仍是将方法customQueueTest1称做task1,将同步函数添加的任务称做task2。这里两个task属于2个不一样的队列,task1由主队列安排执行,task2由本身建立的队列来安排执行。首先主队列安排task1到主线程中执行,当执行到task2的地方时,由本身建立的队列安排task2到主线程中执行(无需等待task1完成),等task2执行完后继续执行task1,因此这里不会形成线程堵塞。


// 自建串行队列异步函数
- (void)customQueueTest2{
    NSLog(@"自建串行队列异步函数");
    dispatch_queue_t serialQueue = dispatch_queue_create("com.test.myQueue", DISPATCH_QUEUE_SERIAL);
    
    for (NSInteger i = 0; i < 5; i++) {
        dispatch_async(serialQueue, ^{
            [NSThread sleepForTimeInterval:1.0f]; // 模拟耗时操做
            NSLog(@"%ld--%@",i,[NSThread currentThread]);
        });
    }
    NSLog(@"----end----");
}

// ****************打印结果****************
2019-12-28 15:52:55.958177+0800 MultithreadingDemo[20363:3934000] 自建串行队列异步函数
2019-12-28 15:52:55.958379+0800 MultithreadingDemo[20363:3934000] ----end----
2019-12-28 15:52:56.962162+0800 MultithreadingDemo[20363:3937135] 0--<NSThread: 0x60000306c780>{number = 10, name = (null)}
2019-12-28 15:52:57.966949+0800 MultithreadingDemo[20363:3937135] 1--<NSThread: 0x60000306c780>{number = 10, name = (null)}
2019-12-28 15:52:58.971743+0800 MultithreadingDemo[20363:3937135] 2--<NSThread: 0x60000306c780>{number = 10, name = (null)}
2019-12-28 15:52:59.977245+0800 MultithreadingDemo[20363:3937135] 3--<NSThread: 0x60000306c780>{number = 10, name = (null)}
2019-12-28 15:53:00.981966+0800 MultithreadingDemo[20363:3937135] 4--<NSThread: 0x60000306c780>{number = 10, name = (null)}
复制代码

可见经过异步函数向本身建立的串行队列中添加的任务是在新开启的线程中执行,并且全部任务都是在同一个子线程中执行(也就是说多个任务只会开启一个子线程),并且是串行执行。


// 自建并发队列同步函数
- (void)customQueueTest3{
    NSLog(@"自建并发队列同步函数<##>");
    dispatch_queue_t concurrentQueue = dispatch_queue_create("com.test.myQueue", DISPATCH_QUEUE_CONCURRENT);
    
    for (NSInteger i = 0; i < 5; i++) {
        dispatch_sync(concurrentQueue, ^{
            [NSThread sleepForTimeInterval:1.0f]; // 模拟耗时操做
            NSLog(@"%ld--%@",i,[NSThread currentThread]);
        });
    }
    NSLog(@"----end----");
}

// ****************打印结果****************
2019-12-28 15:56:47.003113+0800 MultithreadingDemo[20363:3934000] 自建并发队列同步函数
2019-12-28 15:56:48.003737+0800 MultithreadingDemo[20363:3934000] 0--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:56:49.005293+0800 MultithreadingDemo[20363:3934000] 1--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:56:50.006181+0800 MultithreadingDemo[20363:3934000] 2--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:56:51.006946+0800 MultithreadingDemo[20363:3934000] 3--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:56:52.007620+0800 MultithreadingDemo[20363:3934000] 4--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:56:52.007953+0800 MultithreadingDemo[20363:3934000] ----end----
复制代码

可见经过同步函数向本身建立的并发队列中添加的任务是在当前线程中执行,并且是串行执行。


// 自建并发队列异步函数
- (void)customQueueTest4{
    NSLog(@"自建并发队列异步函数<##>");
    dispatch_queue_t concurrentQueue = dispatch_queue_create("com.test.myQueue", DISPATCH_QUEUE_CONCURRENT);
    
    for (NSInteger i = 0; i < 5; i++) {
        dispatch_async(concurrentQueue, ^{
            [NSThread sleepForTimeInterval:1.0f]; // 模拟耗时操做
            NSLog(@"%ld--%@",i,[NSThread currentThread]);
        });
    }
    NSLog(@"----end----");
}

// ****************打印结果****************
2019-12-28 16:01:01.262912+0800 MultithreadingDemo[20363:3934000] 自建并发队列异步函数<##>
2019-12-28 16:01:01.263306+0800 MultithreadingDemo[20363:3934000] ----end----
2019-12-28 16:01:02.265466+0800 MultithreadingDemo[20363:3942614] 1--<NSThread: 0x600003061700>{number = 15, name = (null)}
2019-12-28 16:01:02.265478+0800 MultithreadingDemo[20363:3944671] 0--<NSThread: 0x60000306cec0>{number = 17, name = (null)}
2019-12-28 16:01:02.265608+0800 MultithreadingDemo[20363:3944674] 4--<NSThread: 0x600003061a80>{number = 19, name = (null)}
2019-12-28 16:01:02.265610+0800 MultithreadingDemo[20363:3944673] 3--<NSThread: 0x600003065740>{number = 18, name = (null)}
2019-12-28 16:01:02.265622+0800 MultithreadingDemo[20363:3944672] 2--<NSThread: 0x600003061640>{number = 20, name = (null)}
复制代码

可见经过异步函数向本身建立的并发队列添加的任务不是在当前线程执行,而是多个任务是在不一样的线程中执行,而且是并发执行。


小结:

同步 异步
主队列 没有开启新线程;线程阻塞 没有开启新线程;串行执行任务
全局队列 没有开启新线程;串行执行任务 有开启新线程;并发执行任务
本身建立串行队列 没有开启新线程;串行执行任务 有开启新线程;串行执行任务
本身建立并发队列 没有开启新线程;串行执行任务 有开启新线程;并发执行任务

4.3 队列的挂起与与恢复

当一个派发队列中有任务还没被安排执行时,咱们能够选择将队列挂起,而后在合适的时机恢复执行。挂起和恢复的行数以下:

//挂起指定的dispatch_queue
void dispatch_suspend(dispatch_object_t object);

//恢复指定的dispatch_queue
void dispatch_resume(dispatch_object_t object);
复制代码

注意dispatch_suspend只能挂起还没执行的任务,已经执行和正在执行的任务是没有影响的。并且对主队列执行挂起操做是无效的。

5. 栅栏函数(dispatch_barrier)

栅栏函数是GCD提供的用于阻塞分割任务的一组函数。其主要做用就是在队列中设置栅栏,来人为干预队列中任务的执行顺序。也能够理解为用来设置任务之间的依赖关系。栅栏函数分同步栅栏函数异步栅栏函数

//同步栅栏函数
void dispatch_barrier_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);
void dispatch_barrier_sync_f(dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);

//异步栅栏函数
void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
void dispatch_barrier_async_f(dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
复制代码

同步栅栏函数和异步栅栏函数的异同点:

  • 相同点:它们都将多个任务分割成了3个部分,第一个部分是栅栏函数以前的任务,是最早执行的;第二个部分是栅栏函数添加的任务,这个任务要等栅栏函数以前的任务都执行完了才会执行;第三个部分是栅栏函数以后的任务,这个部分要等栅栏函数里面的任务执行完了才会执行。
  • 不一样点:同步栅栏函数不会开启新线程,其添加的任务在当前线程执行,会阻塞当前线程;异步栅栏函数会开启新线程来执行其添加的任务,不会阻塞当前线程。

假设我如今有这样一个需求:

一个大文件被分红part1和part2两部分存在服务器上,如今要将part1和part2都下载下来后而后合并并写入磁盘。这里其实有4个任务,下载part1是task1,下载part2是task2,合并part1和part2是task3,将合并后的文件写入磁盘是task4。这4个任务执行顺序是task1和task2并发异步执行,这两个任务都执行完了后再执行task3,task3执行完了再执行task4。

经过同步栅栏函数和异步栅栏函数均可以实现这个需求,下面咱们来看看用同步和异步栅栏函数来实现有什么区别。

5.1 同步栅栏函数(dispatch_barrier_sync)

// 同步栅栏函数
- (void)syncBarrier{
    NSLog(@"当前线程1");
    dispatch_queue_t queue = dispatch_queue_create("com.demo.tsk", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
       NSLog(@"开始下载part1---%@",[NSThread currentThread]);
        [NSThread sleepForTimeInterval:2.0f]; // 模拟下载耗时2s
        NSLog(@"完成下载part1---%@",[NSThread currentThread]);
    });
    
    NSLog(@"当前线程2");
    
    dispatch_async(queue, ^{
       NSLog(@"开始下载part2---%@",[NSThread currentThread]);
       [NSThread sleepForTimeInterval:1.0f]; // 模拟下载耗时1s
       NSLog(@"完成下载part2---%@",[NSThread currentThread]);
    });
    
    NSLog(@"当前线程3");
    
    dispatch_barrier_sync(queue, ^{
       NSLog(@"开始合并part1和part2---%@",[NSThread currentThread]);
       [NSThread sleepForTimeInterval:1.0f]; // 模拟下载耗时1s
       NSLog(@"完成合并part1和part2---%@",[NSThread currentThread]);
    });
    
    NSLog(@"当前线程4");
        
    dispatch_async(queue, ^{
       NSLog(@"开始写入磁盘---%@",[NSThread currentThread]);
       [NSThread sleepForTimeInterval:1.0f]; // 模拟下载耗时1s
       NSLog(@"完成写入磁盘---%@",[NSThread currentThread]);
    });
    
    NSLog(@"当前线程5");
}

// ****************打印结果****************
2019-12-29 11:48:52.804380+0800 MultithreadingDemo[28408:4279520] 当前线程1
2019-12-29 11:48:52.805902+0800 MultithreadingDemo[28408:4279520] 当前线程2
2019-12-29 11:48:52.806323+0800 MultithreadingDemo[28408:4279520] 当前线程3
2019-12-29 11:48:52.806282+0800 MultithreadingDemo[28408:4292323] 开始下载part1---<NSThread: 0x6000030c4a80>{number = 13, name = (null)}
2019-12-29 11:48:52.806513+0800 MultithreadingDemo[28408:4280937] 开始下载part2---<NSThread: 0x6000030d0600>{number = 9, name = (null)}
2019-12-29 11:48:53.806988+0800 MultithreadingDemo[28408:4280937] 完成下载part2---<NSThread: 0x6000030d0600>{number = 9, name = (null)}
2019-12-29 11:48:54.809914+0800 MultithreadingDemo[28408:4292323] 完成下载part1---<NSThread: 0x6000030c4a80>{number = 13, name = (null)}
2019-12-29 11:48:54.810426+0800 MultithreadingDemo[28408:4279520] 开始合并part1和part2---<NSThread: 0x6000030a5940>{number = 1, name = main}
2019-12-29 11:48:55.810924+0800 MultithreadingDemo[28408:4279520] 完成合并part1和part2---<NSThread: 0x6000030a5940>{number = 1, name = main}
2019-12-29 11:48:55.811204+0800 MultithreadingDemo[28408:4279520] 当前线程4
2019-12-29 11:48:55.811405+0800 MultithreadingDemo[28408:4279520] 当前线程5
2019-12-29 11:48:55.811455+0800 MultithreadingDemo[28408:4292323] 开始写入磁盘---<NSThread: 0x6000030c4a80>{number = 13, name = (null)}
2019-12-29 11:48:56.816247+0800 MultithreadingDemo[28408:4292323] 完成写入磁盘---<NSThread: 0x6000030c4a80>{number = 13, name = (null)}
复制代码

运行结果能够看出,需求的功能是实现了,可是有个问题,同步栅栏函数在分割任务的同时也阻塞了当前线程,这里当前线程是主线程,这就意味着在task一、task2和task3这3个任务都完成以前,UI界面是出于卡死状态的,这种用户体验显然是很是糟糕的。下面咱们来看看异步栅栏函数来实现这个功能的效果。


5.2 异步栅栏函数(dispatch_barrier_async)

// 异步栅栏函数
- (void)asyncBarrier{
    NSLog(@"当前线程1");
    dispatch_queue_t queue = dispatch_queue_create("com.demo.tsk", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
       NSLog(@"开始下载part1---%@",[NSThread currentThread]);
        [NSThread sleepForTimeInterval:2.0f]; // 模拟下载耗时2s
        NSLog(@"完成下载part1---%@",[NSThread currentThread]);
    });
    
    NSLog(@"当前线程2");
    
    dispatch_async(queue, ^{
       NSLog(@"开始下载part2---%@",[NSThread currentThread]);
       [NSThread sleepForTimeInterval:1.0f]; // 模拟下载耗时1s
       NSLog(@"完成下载part2---%@",[NSThread currentThread]);
    });
    
    NSLog(@"当前线程3");
    
    dispatch_barrier_async(queue, ^{
       NSLog(@"开始合并part1和part2---%@",[NSThread currentThread]);
       [NSThread sleepForTimeInterval:1.0f]; // 模拟下载耗时1s
       NSLog(@"完成合并part1和part2---%@",[NSThread currentThread]);
    });
    
    NSLog(@"当前线程4");
        
    dispatch_async(queue, ^{
       NSLog(@"开始写入磁盘---%@",[NSThread currentThread]);
       [NSThread sleepForTimeInterval:1.0f]; // 模拟下载耗时1s
       NSLog(@"完成写入磁盘---%@",[NSThread currentThread]);
    });
    
    NSLog(@"当前线程5");
}

// ****************打印结果****************
2019-12-29 11:50:08.465656+0800 MultithreadingDemo[28408:4279520] 当前线程1
2019-12-29 11:50:08.466085+0800 MultithreadingDemo[28408:4279520] 当前线程2
2019-12-29 11:50:08.466158+0800 MultithreadingDemo[28408:4292323] 开始下载part1---<NSThread: 0x6000030c4a80>{number = 13, name = (null)}
2019-12-29 11:50:08.466392+0800 MultithreadingDemo[28408:4279520] 当前线程3
2019-12-29 11:50:08.466516+0800 MultithreadingDemo[28408:4293001] 开始下载part2---<NSThread: 0x6000030d0440>{number = 14, name = (null)}
2019-12-29 11:50:08.466586+0800 MultithreadingDemo[28408:4279520] 当前线程4
2019-12-29 11:50:08.466707+0800 MultithreadingDemo[28408:4279520] 当前线程5
2019-12-29 11:50:09.471031+0800 MultithreadingDemo[28408:4293001] 完成下载part2---<NSThread: 0x6000030d0440>{number = 14, name = (null)}
2019-12-29 11:50:10.469408+0800 MultithreadingDemo[28408:4292323] 完成下载part1---<NSThread: 0x6000030c4a80>{number = 13, name = (null)}
2019-12-29 11:50:10.469996+0800 MultithreadingDemo[28408:4292323] 开始合并part1和part2---<NSThread: 0x6000030c4a80>{number = 13, name = (null)}
2019-12-29 11:50:11.475249+0800 MultithreadingDemo[28408:4292323] 完成合并part1和part2---<NSThread: 0x6000030c4a80>{number = 13, name = (null)}
2019-12-29 11:50:11.475594+0800 MultithreadingDemo[28408:4292323] 开始写入磁盘---<NSThread: 0x6000030c4a80>{number = 13, name = (null)}
2019-12-29 11:50:12.478816+0800 MultithreadingDemo[28408:4292323] 完成写入磁盘---<NSThread: 0x6000030c4a80>{number = 13, name = (null)}
复制代码

从上面运行结果能够看出,异步栅栏函数不会阻塞当前线程,也就是说UI界面并不会被卡死。

5.3 注意事项

  • 全局队列对栅栏函数是不生效的,必须是本身建立的并发队列。
  • 全部任务(包括栅栏函数添加的任务)都必须在同一个派发队列中,不然栅栏函数不生效。使用第三方网络框架(好比AFNetworking)进行网络请求时使用栅栏函数无效的正是由于这个缘由致使。

6. 任务组(dispatch_group)

前面栅栏函数实现的需求也能够经过任务组来实现。GCD中关于任务组的API以下:

// 建立一个任务组(任务组本质上是一个值为LONG_MAX的信号量dispatch_semaphore_t)
dispatch_group_t dispatch_group_create(void);

// 向任务组中添加任务的异步函数。
// 参数一:任务组; 参数二:派发队列; 参数三:任务block
void dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);

// 监听group组中任务的完成状态,当全部的任务都执行完成后,触发block块
// 参数一:任务组; 
// 参数二:是第三个参数block所处的派发队列,这个队列和任务组中的任务的队列能够不是同一个队列,好比任务组中的任务都完成后须要刷新UI,那这个队列就是主队列; 
// 参数三:任务组中的任务都完成后要执行的block
void dispatch_group_notify(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);

// dispatch_group_enter是将block任务添加到queue队列,并被group组管理,任务组的任务数+1
// dispatch_group_leave是相应的任务执行完成,任务组的任务数-1
// 这两个API是成对出现的
void dispatch_group_enter(dispatch_group_t group);
void dispatch_group_leave(dispatch_group_t group);

// 会阻塞当前线程,等其前面的任务都执行完了后才会执行其后面的代码
long dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout);
复制代码

现需求以下:

某个界面须要请求banner信息和产品列表信息,等这两个接口的数据都返回后再回到主线程刷新UI。

这个需求经过栅栏函数和任务组均可以实现,任务组能够经过dispatch_asyncdispatch_group_enterdispatch_group_leave这3个API配合使用来实现,也能够经过dispatch_group_async这个API来实现。

6.1 dispatch_asyncdispatch_group_enterdispatch_group_leave

// dispatch_group_enter()、dispatch_group_leave()和dispatch_async()配合使用
- (void)GCDGroup1{
    
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_queue_create("com.demo.tsk", DISPATCH_QUEUE_CONCURRENT);
    
    NSLog(@"当前线程1");
    dispatch_group_enter(group); // 开始任务前将任务交给任务组管理,任务组中任务数+1
    dispatch_async(queue, ^{
        NSLog(@"开始请求banner数据---%@",[NSThread currentThread]);
        [NSThread sleepForTimeInterval:1.0f]; // 模拟下载耗时1s
        NSLog(@"收到banner数据---%@",[NSThread currentThread]);
        dispatch_group_leave(group); // 任务结束后将任务从任务组中移除,任务组中任务数-1
    });
    
    NSLog(@"当前线程2");
    dispatch_group_enter(group); // 任务组中任务数+1
    dispatch_async(queue, ^{
        NSLog(@"开始请求产品列表数据---%@",[NSThread currentThread]);
        [NSThread sleepForTimeInterval:3.0f]; // 模拟下载耗时1s
        NSLog(@"收到产品列表数据---%@",[NSThread currentThread]);
        dispatch_group_leave(group); // 任务组中任务数-1
    });
    
    NSLog(@"当前线程3");
    
    // 监放任务组中的任务的完成状况,当任务组中全部任务都完成时指定队列安排执行block中的代码
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"回到主线程刷新UI---%@",[NSThread currentThread]);
    });
    
    NSLog(@"当前线程4");
}

// ****************打印结果****************
2019-12-30 08:56:19.991427+0800 MultithreadingDemo[35831:4040872] 当前线程1
2019-12-30 08:56:19.991561+0800 MultithreadingDemo[35831:4040872] 当前线程2
2019-12-30 08:56:19.991607+0800 MultithreadingDemo[35831:4040962] 开始请求banner数据---<NSThread: 0x600003f91f80>{number = 7, name = (null)}
2019-12-30 08:56:19.991664+0800 MultithreadingDemo[35831:4040872] 当前线程3
2019-12-30 08:56:19.991708+0800 MultithreadingDemo[35831:4044340] 开始请求产品列表数据---<NSThread: 0x600003f91cc0>{number = 8, name = (null)}
2019-12-30 08:56:19.991738+0800 MultithreadingDemo[35831:4040872] 当前线程4
2019-12-30 08:56:20.992641+0800 MultithreadingDemo[35831:4040962] 收到banner数据---<NSThread: 0x600003f91f80>{number = 7, name = (null)}
2019-12-30 08:56:22.993730+0800 MultithreadingDemo[35831:4044340] 收到产品列表数据---<NSThread: 0x600003f91cc0>{number = 8, name = (null)}
2019-12-30 08:56:22.993924+0800 MultithreadingDemo[35831:4040872] 回到主线程刷新UI---<NSThread: 0x600003fe6d00>{number = 1, name = main}
复制代码

6.2 dispatch_group_async

- (void)GCDGroup2{
    
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_queue_create("com.demo.tsk", DISPATCH_QUEUE_CONCURRENT);
    
    NSLog(@"当前线程1");
    dispatch_group_async(group, queue, ^{
        NSLog(@"开始请求banner数据---%@",[NSThread currentThread]);
        [NSThread sleepForTimeInterval:1.0f]; // 模拟下载耗时1s
        NSLog(@"收到banner数据---%@",[NSThread currentThread]);
    });
    
    NSLog(@"当前线程2");
    dispatch_group_async(group, queue, ^{
        NSLog(@"开始请求产品列表数据---%@",[NSThread currentThread]);
        [NSThread sleepForTimeInterval:3.0f]; // 模拟下载耗时1s
        NSLog(@"收到产品列表数据---%@",[NSThread currentThread]);
    });
    
    NSLog(@"当前线程3");
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"回到主线程刷新UI---%@",[NSThread currentThread]);
    });
    
    NSLog(@"当前线程4");
}

// ****************打印结果****************
2019-12-30 08:59:33.989079+0800 MultithreadingDemo[35831:4040872] 当前线程1
2019-12-30 08:59:33.989212+0800 MultithreadingDemo[35831:4040872] 当前线程2
2019-12-30 08:59:33.989248+0800 MultithreadingDemo[35831:4045455] 开始请求banner数据---<NSThread: 0x600003f9a3c0>{number = 9, name = (null)}
2019-12-30 08:59:33.989299+0800 MultithreadingDemo[35831:4040872] 当前线程3
2019-12-30 08:59:33.989313+0800 MultithreadingDemo[35831:4044340] 开始请求产品列表数据---<NSThread: 0x600003f91cc0>{number = 8, name = (null)}
2019-12-30 08:59:33.989358+0800 MultithreadingDemo[35831:4040872] 当前线程4
2019-12-30 08:59:34.992763+0800 MultithreadingDemo[35831:4045455] 收到banner数据---<NSThread: 0x600003f9a3c0>{number = 9, name = (null)}
2019-12-30 08:59:36.992068+0800 MultithreadingDemo[35831:4044340] 收到产品列表数据---<NSThread: 0x600003f91cc0>{number = 8, name = (null)}
2019-12-30 08:59:36.992348+0800 MultithreadingDemo[35831:4040872] 回到主线程刷新UI---<NSThread: 0x600003fe6d00>{number = 1, name = main}
复制代码

从上面的打印结果能够看出,这两种实现方式的效果是同样的,dispatch_group_notify监放任务组并不会阻塞当前线程。

6.3 dispatch_group_wait与dispatch_group_notify的区别

咱们把上面代码中的dispatch_group_notify换成dispatch_group_wait再看看运行结果。

// dispatch_group_wait的使用
- (void)GCDGroup3{
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_queue_create("com.demo.tsk", DISPATCH_QUEUE_CONCURRENT);
    
    NSLog(@"当前线程1");
    dispatch_group_async(group, queue, ^{
        NSLog(@"开始请求banner数据---%@",[NSThread currentThread]);
        [NSThread sleepForTimeInterval:1.0f]; // 模拟下载耗时1s
        NSLog(@"收到banner数据---%@",[NSThread currentThread]);
    });
    
    NSLog(@"当前线程2");
    dispatch_group_async(group, queue, ^{
        NSLog(@"开始请求产品列表数据---%@",[NSThread currentThread]);
        [NSThread sleepForTimeInterval:3.0f]; // 模拟下载耗时1s
        NSLog(@"收到产品列表数据---%@",[NSThread currentThread]);
    });
    
    NSLog(@"当前线程3");
//    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
//        NSLog(@"回到主线程刷新UI---%@",[NSThread currentThread]);
//    });
    
    // 将等待时间设置为DISPATCH_TIME_FOREVER,表示永不超时,等任务组中任务所有都完成后才会执行其后面的代码
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    
    NSLog(@"任务组中的任务所有完成,刷新UI");
    
    NSLog(@"当前线程4");
}

// ****************打印结果****************
2019-12-30 09:10:12.841430+0800 MultithreadingDemo[36021:4052387] 当前线程1
2019-12-30 09:10:12.841566+0800 MultithreadingDemo[36021:4052387] 当前线程2
2019-12-30 09:10:12.841604+0800 MultithreadingDemo[36021:4052474] 开始请求banner数据---<NSThread: 0x60000111dcc0>{number = 2, name = (null)}
2019-12-30 09:10:12.841660+0800 MultithreadingDemo[36021:4052387] 当前线程3
2019-12-30 09:10:12.841704+0800 MultithreadingDemo[36021:4052591] 开始请求产品列表数据---<NSThread: 0x600001151680>{number = 6, name = (null)}
2019-12-30 09:10:13.842615+0800 MultithreadingDemo[36021:4052474] 收到banner数据---<NSThread: 0x60000111dcc0>{number = 2, name = (null)}
2019-12-30 09:10:15.842548+0800 MultithreadingDemo[36021:4052591] 收到产品列表数据---<NSThread: 0x600001151680>{number = 6, name = (null)}
2019-12-30 09:10:15.842738+0800 MultithreadingDemo[36021:4052387] 任务组中的任务所有完成,刷新UI
2019-12-30 09:10:15.842826+0800 MultithreadingDemo[36021:4052387] 当前线程4
复制代码

结果发现dispatch_group_wait虽然实现了需求,可是有个问题,它阻塞了当前线程,效果和同步栅栏函数同样。

对于dispatch_group_wait函数的第二个参数需特别说明一下,设置为DISPATCH_TIME_FOREVER表示永不超时,等任务组中任务所有都完成后才会执行其后面的代码。但若是将其设置为一个具体的时间,好比下面设置为2秒,那么若是任务组中的全部任务须要1秒执行完,那么就只须要等待1秒就能够执行后面的代码;若是任务组中所有任务执行完要3秒,那么等待2秒后就会执行后面的代码。

dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)));
复制代码

7 dispatch_after和dispatch_time_t

7.1 dispatch_after

dispatch_after是GCD中用于延迟将某个任务添加到队列中,其定义以下:

void dispatch_after(dispatch_time_t when, 
                    dispatch_queue_t queue, 
                    dispatch_block_t block);
复制代码
  • 第一个参数when:数据类型是dispatch_time_t(后面会详细介绍),表示延迟多长时间开始执行。
  • 第二个参数queue:管理要延迟执行的任务的派发队列。
  • 第三个参数block:要延迟执行的任务块。

好比我要从如今开始,延迟3秒后在主线程刷新UI,其代码以下。

// dispatch_after
// 需求:从如今开始,延迟3秒后在主线程刷新UI。
- (void)dispatchAfter{
    NSLog(@"如今时间--%@",[NSDate date]);
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"到主线程刷新UI--%@",[NSDate date]);
    });
}

// ****************打印结果****************
2019-12-30 10:19:00.397355+0800 MultithreadingDemo[36250:4082843] 如今时间--2019-12-30 02:19:00 +0000
2019-12-30 10:19:03.397714+0800 MultithreadingDemo[36250:4082843] 到主线程刷新UI--2019-12-30 02:19:03 +0000
复制代码

7.2 dispatch_time_t

建立dispatch_time_t类型数据的函数有2个:dispatch_timedispatch_walltime

7.2.1 dispatch_time

dispatch_time定义以下:

dispatch_time_t dispatch_time(dispatch_time_t when, int64_t delta);
复制代码
  • 参数一when:也是一个dispatch_time_t类型的数据,表示从什么时间开始,通常直接传 DISPATCH_TIME_NOW 表示从如今开始。
  • 参数二delta:表示具体的时间长度,delta的单位是纳秒,因此不能直接传 int 或 float, 须要写成这种形式(int64_t)3 * NSEC_PER_SEC来表示3秒。 前面dispatch_after的示例代码中就是用dispatch_time来建立的dispatch_time_t,下面咱们主要介绍一下第二个参数中用到的一些关于时间的宏:
#define NSEC_PER_SEC 1000000000ull 每秒有1000000000纳秒
#define NSEC_PER_MSEC 1000000ull 每毫秒有1000000纳秒
#define USEC_PER_SEC 1000000ull 每秒有1000000微秒
#define NSEC_PER_USEC 1000ull 每微秒有1000纳秒
复制代码

再次强调一下delta单位是纳秒,因此咱们表示1秒能够有以下几种写法:

  • 1 * NSEC_PER_SEC
  • 1000 * NSEC_PER_MSEC (表示1000毫秒)
  • USEC_PER_SEC * NSEC_PER_USEC
7.2.2 dispatch_walltime

dispatch_walltime定义以下:

dispatch_time_t dispatch_walltime(const struct timespec *_Nullable when, int64_t delta);
复制代码
  • 参数一when:表示从什么时间开始,是一个结构体,能够建立一个绝对的时间点(好比2019-12-30 10:50:55)。也能够传NULL表示从当前时间开始。
  • 参数二delta:和dispatch_time函数的第二个参数同样。
// dispatch_walltime
// 需求:从一个具体时间点开始,再晚10秒执行任务
- (void)dispatchWallTime{
    NSString *dateStr = @"2019-12-30 11:09:00";
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    formatter.dateFormat = @"yyyy-MM-dd HH:mm:ss";
    formatter.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT+0800"];
    NSDate *date = [formatter dateFromString:dateStr];
    NSTimeInterval timeInterval = [date timeIntervalSince1970];
    
    // dispatch_walltime第一个参数的结构体
    struct timespec timeStruct;
    timeStruct.tv_sec = (NSInteger)timeInterval;
    
    NSLog(@"设置的时间点--%@",[formatter stringFromDate:date]);
    
    // 比时间点再晚10秒
    dispatch_time_t time = dispatch_walltime(&timeStruct, (int64_t)(10 * NSEC_PER_SEC));
    dispatch_after(time, dispatch_get_main_queue(), ^{
        NSLog(@"到主线程刷新UI--%@",[formatter stringFromDate:[NSDate date]]);
    });
}

// ****************打印结果****************
2019-12-30 11:08:40.525710+0800 MultithreadingDemo[36430:4106292] 设置的时间点--2019-12-30 11:09:00
2019-12-30 11:09:10.000208+0800 MultithreadingDemo[36430:4106292] 到主线程刷新UI--2019-12-30 11:09:10
复制代码

二者之间的区别:

  • dispatch_time建立的是一个相对时间,它参考的是当前系统的时钟,当设备进入休眠后,系统时钟也会进入休眠状态,此时dispatch_time也会被挂起。好比10:00分开始执行dispatch_time,而且60分钟后执行某个任务。10:10分设备休眠了,10:40分设备从休眠中唤醒(共休眠了30分钟),那么从唤醒时刻开始,再等待50分钟(也就是11:30分)才会执行任务。
  • dispatch_walltime建立的是一个绝对的时间点,好比上面的例子,一旦建立就表示从10:00开始,60分钟以后(也就是11:00)执行任务,它不会受到休眠的影响。

8. 快速迭代方法:dispatch_apply()

dispatch_apply相似for循环,会在指定的队列中屡次执行任务。其定义以下:

void dispatch_apply(size_t iterations,     
                    dispatch_queue_t queue,
                    DISPATCH_NOESCAPE void (^block)(size_t));
复制代码
  • 参数一iterations:执行的次数
  • 参数二queue:提交任务的队列
  • 参数三block:执行任务的代码块
- (void)dispatchApply{
    NSLog(@"开始");
    dispatch_queue_t queue = dispatch_queue_create("com.demo.tsk", DISPATCH_QUEUE_CONCURRENT);
    dispatch_apply(10, queue, ^(size_t index) {
        NSLog(@"第%ld次开始执行--%@",index,[NSThread currentThread]);
        [NSThread sleepForTimeInterval:3.0f];
        NSLog(@"第%ld次结束执行--%@",index,[NSThread currentThread]);
    });
    NSLog(@"结束");
}

// ****************打印结果****************
2019-12-30 11:59:35.025320+0800 MultithreadingDemo[36604:4129509] 开始
2019-12-30 11:59:35.025457+0800 MultithreadingDemo[36604:4129509] 第0次开始执行--<NSThread: 0x600003095680>{number = 1, name = main}
2019-12-30 11:59:35.025616+0800 MultithreadingDemo[36604:4129600] 第1次开始执行--<NSThread: 0x60000309cb00>{number = 4, name = (null)}
2019-12-30 11:59:35.025719+0800 MultithreadingDemo[36604:4129599] 第2次开始执行--<NSThread: 0x6000030e92c0>{number = 5, name = (null)}
2019-12-30 11:59:35.025735+0800 MultithreadingDemo[36604:4129598] 第3次开始执行--<NSThread: 0x6000030ed240>{number = 6, name = (null)}
2019-12-30 11:59:38.025791+0800 MultithreadingDemo[36604:4129509] 第0次结束执行--<NSThread: 0x600003095680>{number = 1, name = main}
2019-12-30 11:59:38.025944+0800 MultithreadingDemo[36604:4129599] 第2次结束执行--<NSThread: 0x6000030e92c0>{number = 5, name = (null)}
2019-12-30 11:59:38.025945+0800 MultithreadingDemo[36604:4129600] 第1次结束执行--<NSThread: 0x60000309cb00>{number = 4, name = (null)}
2019-12-30 11:59:38.025951+0800 MultithreadingDemo[36604:4129598] 第3次结束执行--<NSThread: 0x6000030ed240>{number = 6, name = (null)}
2019-12-30 11:59:38.026063+0800 MultithreadingDemo[36604:4129509] 第4次开始执行--<NSThread: 0x600003095680>{number = 1, name = main}
2019-12-30 11:59:38.026082+0800 MultithreadingDemo[36604:4129600] 第6次开始执行--<NSThread: 0x60000309cb00>{number = 4, name = (null)}
2019-12-30 11:59:38.026086+0800 MultithreadingDemo[36604:4129599] 第5次开始执行--<NSThread: 0x6000030e92c0>{number = 5, name = (null)}
2019-12-30 11:59:38.026145+0800 MultithreadingDemo[36604:4129598] 第7次开始执行--<NSThread: 0x6000030ed240>{number = 6, name = (null)}
2019-12-30 11:59:41.027346+0800 MultithreadingDemo[36604:4129600] 第6次结束执行--<NSThread: 0x60000309cb00>{number = 4, name = (null)}
2019-12-30 11:59:41.027346+0800 MultithreadingDemo[36604:4129509] 第4次结束执行--<NSThread: 0x600003095680>{number = 1, name = main}
2019-12-30 11:59:41.027384+0800 MultithreadingDemo[36604:4129598] 第7次结束执行--<NSThread: 0x6000030ed240>{number = 6, name = (null)}
2019-12-30 11:59:41.027402+0800 MultithreadingDemo[36604:4129599] 第5次结束执行--<NSThread: 0x6000030e92c0>{number = 5, name = (null)}
2019-12-30 11:59:41.027504+0800 MultithreadingDemo[36604:4129600] 第8次开始执行--<NSThread: 0x60000309cb00>{number = 4, name = (null)}
2019-12-30 11:59:41.027506+0800 MultithreadingDemo[36604:4129509] 第9次开始执行--<NSThread: 0x600003095680>{number = 1, name = main}
2019-12-30 11:59:44.027923+0800 MultithreadingDemo[36604:4129600] 第8次结束执行--<NSThread: 0x60000309cb00>{number = 4, name = (null)}
2019-12-30 11:59:44.027923+0800 MultithreadingDemo[36604:4129509] 第9次结束执行--<NSThread: 0x600003095680>{number = 1, name = main}
2019-12-30 11:59:44.028156+0800 MultithreadingDemo[36604:4129509] 结束
复制代码

由打印结果能够看出对于并发队列dispatch_apply会建立多个线程去并发执行,并且会阻塞当前线程,等全部任务都完成后才会继续执行后面的代码。

对于串行队列不会开启新的线程,而是会在当前线程中串行执行。

9. dispatch_once

GCD提供了dispatch_once()函数保证在应用程序生命周期中只执行一次指定处理。好比来生成单例。

- (void)dispatchOnce{
    static ViewController *vc = nil;
    static dispatch_once_t onceToken;
    dispatch_apply(3, dispatch_get_global_queue(0, 0), ^(size_t idx) {
        NSLog(@"第%ld次开始执行--%@",idx,[NSThread currentThread]);
        dispatch_once(&onceToken, ^{
            vc = [[ViewController alloc] init];
            NSLog(@"是否只执行了一次--%@",[NSThread currentThread]);
        });
        NSLog(@"第%ld次结束执行--%@",idx,[NSThread currentThread]);
    });
}

// ****************打印结果****************
2019-12-30 12:14:43.911414+0800 MultithreadingDemo[36690:4137229] 第0次开始执行--<NSThread: 0x600002b7e0c0>{number = 1, name = main}
2019-12-30 12:14:43.911421+0800 MultithreadingDemo[36690:4137379] 第1次开始执行--<NSThread: 0x600002b1df40>{number = 6, name = (null)}
2019-12-30 12:14:43.911461+0800 MultithreadingDemo[36690:4137378] 第2次开始执行--<NSThread: 0x600002b0c280>{number = 7, name = (null)}
2019-12-30 12:14:43.911613+0800 MultithreadingDemo[36690:4137229] 是否只执行了一次--<NSThread: 0x600002b7e0c0>{number = 1, name = main}
2019-12-30 12:14:43.911748+0800 MultithreadingDemo[36690:4137229] 第0次结束执行--<NSThread: 0x600002b7e0c0>{number = 1, name = main}
2019-12-30 12:14:43.911750+0800 MultithreadingDemo[36690:4137378] 第2次结束执行--<NSThread: 0x600002b0c280>{number = 7, name = (null)}
2019-12-30 12:14:43.911765+0800 MultithreadingDemo[36690:4137379] 第1次结束执行--<NSThread: 0x600002b1df40>{number = 6, name = (null)}
复制代码

10. dispatch_semaphore(信号量)

dispatch_semaphore用于控制最大并发数。其主要涉及到3个函数:


// 建立信号量API
dispatch_semaphore_t dispatch_semaphore_create(long value);
复制代码

dispatch_semaphore_create()建立并返回一个dispatch_semaphore_t类型的信号量,传入的参数必须大于等于0,不然会返回NULL。传入的参数value就是信号量的初始值,也能够理解为最大并发数。当这个值设置为1时,最大并发数为1,能够当成锁来使用。


long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
复制代码

dispatch_semaphore_wait()函数的第一个参数是信号量,第二个参数是等待超时时间。这个函数首先会判断信号量的值是否大于0,若是大于0,那么信号值减1并继续执行后续代码;若是信号值等于0,那么就阻塞当前线程进行等待,直到信号值大于0或等待超时时才会继续执行后续代码。


// 发送信号
long dispatch_semaphore_signal(dispatch_semaphore_t dsema);
复制代码

dispatch_semaphore_signal()函数用来发送信号,发送信号后信号量的值会+1,它可使处于等待状态的线程被唤醒。


// 信号量
- (void)dispatchSemaphore{
    
    dispatch_queue_t queue = dispatch_queue_create("com.demo.tsk", DISPATCH_QUEUE_CONCURRENT);
    
    // 建立信号量并设置信号值(最大并发数)为2
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
    
    for (NSInteger i = 0; i < 5; i++) {
        // 若是信号值大于0,信号值减1并执行后续代码
        // 若是信号值等于0,当前线程将被阻塞处于等待状态,直到信号值大于0或者等待超时为止
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        dispatch_async(queue, ^{
            NSLog(@"第%ld次开始执行--%@",i,[NSThread currentThread]);
            [NSThread sleepForTimeInterval:1.0f];
            NSLog(@"第%ld次结束执行--%@",i,[NSThread currentThread]);
            // 任务执行完后发送信号使信号值+1
            dispatch_semaphore_signal(semaphore);
        });
    }
    
    NSLog(@"******当前线程******");
}

// ****************打印结果****************
2019-12-30 14:33:16.757992+0800 MultithreadingDemo[36982:4182131] 第0次开始执行--<NSThread: 0x600001a11500>{number = 3, name = (null)}
2019-12-30 14:33:17.762332+0800 MultithreadingDemo[36982:4182131] 第0次结束执行--<NSThread: 0x600001a11500>{number = 3, name = (null)}
2019-12-30 14:33:17.762541+0800 MultithreadingDemo[36982:4182131] 第1次开始执行--<NSThread: 0x600001a11500>{number = 3, name = (null)}
2019-12-30 14:33:18.764500+0800 MultithreadingDemo[36982:4182131] 第1次结束执行--<NSThread: 0x600001a11500>{number = 3, name = (null)}
2019-12-30 14:33:18.764710+0800 MultithreadingDemo[36982:4182131] 第2次开始执行--<NSThread: 0x600001a11500>{number = 3, name = (null)}
2019-12-30 14:33:19.766611+0800 MultithreadingDemo[36982:4182131] 第2次结束执行--<NSThread: 0x600001a11500>{number = 3, name = (null)}
2019-12-30 14:33:19.766765+0800 MultithreadingDemo[36982:4182131] 第3次开始执行--<NSThread: 0x600001a11500>{number = 3, name = (null)}
2019-12-30 14:33:20.770737+0800 MultithreadingDemo[36982:4182131] 第3次结束执行--<NSThread: 0x600001a11500>{number = 3, name = (null)}
2019-12-30 14:33:20.770932+0800 MultithreadingDemo[36982:4182040] ******当前线程******
2019-12-30 14:33:20.770944+0800 MultithreadingDemo[36982:4182131] 第4次开始执行--<NSThread: 0x600001a11500>{number = 3, name = (null)}
2019-12-30 14:33:21.771514+0800 MultithreadingDemo[36982:4182131] 第4次结束执行--<NSThread: 0x600001a11500>{number = 3, name = (null)}
复制代码

11. dispatch_source(实现定时器)

dispatch_source是GCD中的一个基本类型,从字面意思可称为调度源,它的做用是当有一些特定的较底层的系统事件发生时,调度源会捕捉到这些事件,而后能够作其余的逻辑处理,调度源有多种类型,分别监听对应类型的系统事件。关于dispatch_source的详细介绍能够参考文章(iOS dispatch_source_t的理解)。

下面咱们经过dispatch_source来实现一个倒计时的功能。

- (void)dispatchSource{
    
    __block NSInteger timeout = 10; // 倒计时时间
    dispatch_queue_t queue = dispatch_queue_create("my.queue", DISPATCH_QUEUE_CONCURRENT);
    
    /*
     建立一个dispatch_source_t对象(其本质是一个OC对象)
     第一个参数是要监听的事件的类型
     第4个参数是回调函数所在的队列
     第2和第3个参数是和监听事件类型(第一个参数)有关的,监听事件类型是DISPATCH_SOURCE_TYPE_TIMER时这两个参数都设置为0就能够了。
     具体的能够参考博客 https://www.cnblogs.com/wjw-blog/p/5903441.html
     */
    self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    
    /*
     设置计时器的一些参数
     第一个参数是前面建立的dispatch_source_t对象
     第二个参数是计时器开始的时间
     第三个参数是计时器间隔时间
     第四个参数是是一个微小的时间量,单位是纳秒,系统为了改进性能,会根据这个时间来推迟timer的执行以与其它系统活动同步。也就是设置timer容许的偏差。
     */
    dispatch_source_set_timer(self.timer, DISPATCH_TIME_NOW, 1.0*NSEC_PER_SEC, 0);
    
    // 设置回调事件
    dispatch_source_set_event_handler(self.timer, ^{
        timeout--;
        NSLog(@"%ld",timeout);
        if (timeout <= 0) {
            // 结束倒计时
            dispatch_source_cancel(self.timer);
        }
    });
    dispatch_resume(self.timer);
}
复制代码

上面只是简单经过GCD实现了计时器功能,咱们彻底能够基于GCD本身封装一个和NSTimer功能相似的计时器。由于NSTimer是基于Runloop的,使用过程当中常常会遇到一些坑,并且NSTimer计时器没有GCD计时器精准。

本身封装GCD计时器时必定要注意,dispatch_source_t类型的timer不要定义为局部变量,不然定时器不起做用。

相关文章
相关标签/搜索