GCD

作者: Liuny | 来源:发表于2018-04-17 16:12 被阅读0次

同步、异步、串行、并行的概念

同步/异步:指的是能否开启新的线程,同步不能开启新的线程,异步可以。
串行/并行:指的是任务的执行方式,串行是指有多个任务时,各个任务按顺序执行,完成一个之后才能进行下一个。并行指的是多个任务可以同时执行。
异步是多个任务并行的前提条件。

名称 特点
同步执行 不具备开启新线程的能力,
任务创建后要执行完才能继续往下走
异步执行 具备开启新线程的能力,
任务创建后可以先绕过,然后再执行
串行队列 队列中的任务要按顺序执行
并行队列 队列中的任务同时执行

线程、任务、队列的概念

名称 特点
线程 程序执行任务的最小调度单位
任务 说白了就是一段代码,在GCD中,
任务就是Block中要执行的内容
队列 用来存放“任务”的一个数组

所有组合

并行队列 串行队列 主队列
异步执行 开启多个新线程,任务同时执行 开启一个新线程,任务按顺序执行 不开启新的线程,任务按顺序执行
同步执行 不开启新线程,任务按顺序执行 不开启新线程,任务按顺序执行 死锁

死锁:两个(多个)线程都要等待对方完成某个操作才能进行下一步,这时就会发生死锁。

代码编程实现

获取队列(三种方式)

1、自定义队列

//自定义并行队列
-(dispatch_queue_t)createConcurrentQueue{
    dispatch_queue_t queue = dispatch_queue_create("LN_Concurrent", DISPATCH_QUEUE_CONCURRENT);
    return queue;
}

//自定义串行队列
-(dispatch_queue_t)createSerialQueue{
    dispatch_queue_t queue = dispatch_queue_create("LN_Serial", DISPATCH_QUEUE_SERIAL);
    return queue;
}

2、主线程串行队列

//获取主线程串行队列
-(dispatch_queue_t)getMainSerialQueue{
    dispatch_queue_t queue = dispatch_get_main_queue();
    return queue;
}

3、全局并发队列

//获取全局并发队列
-(dispatch_queue_t)getGlobalConcurrentQueue{
    /*
     * 第一个参数:优先级别
     DISPATCH_QUEUE_PRIORITY_HIGH
     DISPATCH_QUEUE_PRIORITY_DEFAULT
     DISPATCH_QUEUE_PRIORITY_LOW
     DISPATCH_QUEUE_PRIORITY_GACKGROUND
     */
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
    return queue;
}

为队列添加任务(两种方式)

1、异步添加任务

//异步
-(void)addTaskWithAsyncInQueue:(dispatch_queue_t)queue{
    dispatch_async(queue, ^{
        NSLog(@"任务1开始");
        sleep(5);
        NSLog(@"任务1结束");
    });
    dispatch_async(queue, ^{
        NSLog(@"任务2开始");
        sleep(2);
        NSLog(@"任务2结束");
    });
    dispatch_async(queue, ^{
        NSLog(@"任务3开始");
        sleep(1);
        NSLog(@"任务3结束");
    });
}

2、同步添加任务

-(void)addTaskWithSyncInQueue:(dispatch_queue_t)queue{
    dispatch_sync(queue, ^{
        NSLog(@"任务1开始");
        sleep(5);
        NSLog(@"任务1结束");
    });
    dispatch_sync(queue, ^{
        NSLog(@"任务2开始");
        sleep(2);
        NSLog(@"任务2结束");
    });
    dispatch_sync(queue, ^{
        NSLog(@"任务3开始");
        sleep(1);
        NSLog(@"任务3结束");
    });
}

组合执行

(一)异步+并行

//异步+并行
-(void)lnAsyncConcurrent{
    dispatch_queue_t queue = [self createConcurrentQueue];
    NSLog(@"======start=====");
    [self addTaskWithAsyncInQueue:queue];
    NSLog(@"======end=====");
}

执行输出结果:

2018-04-17 14:28:03.797234+0800 ThreadProject[1708:124655] ======start=====
2018-04-17 14:28:03.797451+0800 ThreadProject[1708:124655] ======end=====
2018-04-17 14:28:03.797510+0800 ThreadProject[1708:124714] 任务1开始
2018-04-17 14:28:03.797512+0800 ThreadProject[1708:124711] 任务3开始
2018-04-17 14:28:03.797512+0800 ThreadProject[1708:124713] 任务2开始
2018-04-17 14:28:04.802118+0800 ThreadProject[1708:124711] 任务3结束
2018-04-17 14:28:05.799360+0800 ThreadProject[1708:124713] 任务2结束
2018-04-17 14:28:08.801884+0800 ThreadProject[1708:124714] 任务1结束

在代码的任务3中设置断点,查看线程数


开启了三个线程
总结:
- 开了三个新线程
- 函数在执行时,先打印了start和end,再回头执行这三个任务
这是异步执行的结果,异步执行会开启新线程,任务可以先绕过不执行,回头再来执行。
- 三个任务同时开始
这是并发的结果

(二)异步+串行

//异步+串行
-(void)lnAsyncSerial{
    dispatch_queue_t queue = [self createSerialQueue];
    NSLog(@"======start=====");
    [self addTaskWithAsyncInQueue:queue];
    NSLog(@"======end=====");
}

执行输出结果:

2018-04-17 15:35:17.971527+0800 ThreadProject[2071:164583] ======start=====
2018-04-17 15:35:17.971778+0800 ThreadProject[2071:164583] ======end=====
2018-04-17 15:35:17.971823+0800 ThreadProject[2071:164636] 任务1开始
2018-04-17 15:35:22.974270+0800 ThreadProject[2071:164636] 任务1结束
2018-04-17 15:35:22.974649+0800 ThreadProject[2071:164636] 任务2开始
2018-04-17 15:35:24.978868+0800 ThreadProject[2071:164636] 任务2结束
2018-04-17 15:35:24.979185+0800 ThreadProject[2071:164636] 任务3开始
2018-04-17 15:35:25.983574+0800 ThreadProject[2071:164636] 任务3结束
开启了新线程
总结:相比异步+并行,这个的任务执行顺序是一个一个来的,上一个任务结束了才开始下一个
任务。
这是串行的结果

(三)异步+主队列

//异步+主队列
-(void)lnAsyncMain{
    dispatch_queue_t queue = dispatch_get_main_queue();
    NSLog(@"======start=====");
    [self addTaskWithAsyncInQueue:queue];
    NSLog(@"======end=====");
}

执行输出结果:

2018-04-17 15:41:25.099769+0800 ThreadProject[2071:164583] ======start=====
2018-04-17 15:41:25.099955+0800 ThreadProject[2071:164583] ======end=====
2018-04-17 15:41:25.101869+0800 ThreadProject[2071:164583] 任务1开始
2018-04-17 15:41:30.103308+0800 ThreadProject[2071:164583] 任务1结束
2018-04-17 15:41:30.103591+0800 ThreadProject[2071:164583] 任务2开始
2018-04-17 15:41:32.104805+0800 ThreadProject[2071:164583] 任务2结束
2018-04-17 15:41:32.105079+0800 ThreadProject[2071:164583] 任务3开始
2018-04-17 15:42:34.792503+0800 ThreadProject[2071:164583] 任务3结束
未开启新线程
总结:执行输出结果与异步+串行是一样的,这是因为主队列就是一个串行队列。
不同的是:不开启新的线程,而是在主线程上运行。

(四)同步+并行

//同步+并行
-(void)lnSyncConcurrent{
    dispatch_queue_t queue = [self createConcurrentQueue];
    NSLog(@"======start=====");
    [self addTaskWithSyncInQueue:queue];
    NSLog(@"======end=====");
}

执行输出结果:

2018-04-17 15:47:48.893351+0800 ThreadProject[2071:164583] ======start=====
2018-04-17 15:47:48.893553+0800 ThreadProject[2071:164583] 任务1开始
2018-04-17 15:47:53.894956+0800 ThreadProject[2071:164583] 任务1结束
2018-04-17 15:47:53.895313+0800 ThreadProject[2071:164583] 任务2开始
2018-04-17 15:47:55.896732+0800 ThreadProject[2071:164583] 任务2结束
2018-04-17 15:47:55.897079+0800 ThreadProject[2071:164583] 任务3开始
2018-04-17 15:47:56.898450+0800 ThreadProject[2071:164583] 任务3结束
2018-04-17 15:47:56.898782+0800 ThreadProject[2071:164583] ======end=====
总结:根据程序代码从上往下走,不开启新线程。

(五)同步+串行
输出结果与同步+并行是相同的。

总结:
同步+并行与同步+串行的区别:同步+并行使用嵌套调用不会产生死锁,同步+串行嵌套调用会产生死锁。

(六)同步+主队列

//同步+主队列
-(void)lnSyncMain{
    dispatch_queue_t queue = dispatch_get_main_queue();
    NSLog(@"======start=====");
    [self addTaskWithSyncInQueue:queue];
    NSLog(@"======end=====");
}

死锁

死锁产生原因:主队列上先有了一个lnSyncMain这个任务,在lnSyncMain方法中又在主队列上添加了任务。由于是串行,先要lnSyncMain这个任务完成,才执行后添加的任务。但是lnSyncMain这个任务的完成又依赖于添加的block。所以就出现了循环等待,导致死锁。

死锁测试:

//嵌套 同步+并行 (不会产生死锁)
-(void)testForLock{
    dispatch_queue_t queue = [self createConcurrentQueue];
    NSLog(@"======start=====");
    dispatch_sync(queue, ^{
        NSLog(@"任务1开始");
        dispatch_sync(queue, ^{
            NSLog(@"任务2开始");
            NSLog(@"任务2结束");
        });
        NSLog(@"任务1结束");
    });
    NSLog(@"======end=====");
}

//嵌套 同步+串行(会产生死锁)
-(void)testForLockTwo{
    dispatch_queue_t queue = [self createSerialQueue];
    NSLog(@"======start=====");
    dispatch_sync(queue, ^{
        NSLog(@"任务1开始");
        dispatch_sync(queue, ^{
            NSLog(@"任务2开始");
            NSLog(@"任务2结束");
        });
        NSLog(@"任务1结束");
    });
    NSLog(@"======end=====");
}

注意:不要嵌套使用同步执行的串行队列任务

GCD其他方法

  • dispatch_once
    保证在app运行期间,block中的代码只执行一次。常用于单例的初始化。
  • dispatch_barrier_async
    1、在并行队列中,等待在dispatch_barrier_async之前加入的任务全部执行完成之后(这些任务是并发执行的)
    2、再执行dispatch_barrier_async中的任务
    3、dispatch_barrier_async中的任务执行完成之后,再去执行在dispatch_barrier_async之后加入到队列中的任务(这些任务是并发执行的)。
    使用场景:多读单写
//异步栅栏(多读单写场景)
-(void)lnAsyncBarrier{
    dispatch_queue_t queue = [self createConcurrentQueue];
    NSLog(@"======start=====");
    [self addTaskWithAsyncInQueue:queue];
    /*
     *1、等待dispatch_barrier_async之前的任务全部执行完
     *2、执行dispatch_barrier_async的任务
     *3、执行dispatch_barrier_async之后的任务
     */
    dispatch_barrier_async(queue, ^{
        NSLog(@"栅栏方法");
    });
    dispatch_async(queue, ^{
        NSLog(@"任务5开始");
        sleep(3);
        NSLog(@"任务5结束");
    });
    dispatch_async(queue, ^{
        NSLog(@"任务6开始");
        sleep(1);
        NSLog(@"任务6结束");
    });
    NSLog(@"======end=====");
}
  • dispatch_group_notify
    结合dispatch_group_t一起使用,等待组里的任务全部完成后,调用dispatch_group_notify的block
    使用场景:同时下载多个图片,所有图片下载完成之后去更新UI(回到主线程)
//group queue
-(void)lnGroupQueue{
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = [self createConcurrentQueue];
    //假设这个数组用于存放图片的下载地址
    NSArray *arrayURLs = @[@"图片下载地址1",@"图片下载地址2",@"图片下载地址3"];
    for(NSString *url in arrayURLs){
        dispatch_group_async(group, queue, ^{
            //根据url去下载图片
            
            NSLog(@"%@",url);
        });
    }
    //主线程上操作
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 当添加到组中的所有任务执行完成之后会调用该Block
        NSLog(@"所有图片已全部下载完成");
    });
}

项目源码

相关文章

  • 多线程之GCD

    GCD介绍 1、GCD简介 2、GCD任务和队列 3、GCD 的基本使用 4、GCD 线程间的通信 5、GCD 的...

  • 扩展GCD(求逆元,解同余方程等等)

    首先要知道gcd函数的基本性质:gcd(a,b)=gcd(b,a)=gcd(|a|,|b|)=gcd(b,a%b)...

  • iOS - GCD

    目录 GCD简介 GCD核心概念 GCD队列的使用 GCD的常见面试题 GCD简介 Grand Central D...

  • iOS-多线程:GCD

    GCD 简介 GCD 任务和队列 GCD 的使用步骤 GCD 的基本使用(6种不同组合区别) GCD 线程间的通信...

  • 浅析GCD

    GCD目录: 1. GCD简介 为什么要用GCD呢? GCD可用于多核的并行运算GCD会自动利用更多的CPU内核(...

  • 7.3 多线程-GCD

    多线程-GCD 多线程-GCD-串行并行 多线程-GCD.png GCD-线程的通讯、延时操作、定时器 GCD-线...

  • iOS 多线程--GCD

    一、GCD基本介绍 1.GCD简介 GCD是Grand Central Dispatch的缩写,GCD是苹果推出的...

  • 自用算法模板(JAVA版)

    一、数论 1)GCD GCD(求最大公约数) QGCD(快速GCD) extGCD(拓展GCD,解决ax + by...

  • GCD介绍

    一、GCD简单介绍 什么是GCD GCD优势 任务和队列 GCD有2个核心概念 GCD的使用就2个步骤 将任务添加...

  • 7.多线程基础(七)GCD加强

    1.GCD串行队列和并发队列 2.GCD延时执行 3.GCD线程组:(的作用) 4.GCD定时器: GCD的实现 ...

网友评论

      本文标题:GCD

      本文链接:https://www.haomeiwen.com/subject/rnjukftx.html