GCD

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

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

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

线程、任务、队列的概念

名称 特点
线程 程序执行任务的最小调度单位
任务 说白了就是一段代码,在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(@"所有图片已全部下载完成");
    });
}

项目源码

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容