多线程GCD.

        最近颇花了一番功夫把多线程GCD人的一些用法总结出来,一来帮自己巩固一下知识、二来希望能帮到对这一块还迷茫的同学。

        GCD是Grand Central Dispatch这三个英文单词的缩写,它是苹果为多核的并行运算提出的解决方案,所以会自动合理地利用更多的CPU内核(比如双核、四核),最重要的是它会自动管理线程的生命周期(创建线程、调度任务、销毁线程),完全不需要我们管理,我们只需要告诉干什么就行。同时它使用的也是c语言,不过由于使用了Block(Swift里叫做闭包),使得使用起来更加方便,而且灵活。

一、任务和队列

在GCD中,加入了两个非常重要的概念:任务和队列。

任务:即操作,你想要干什么,说白了就是一段代码,在GCD中就是一个Block,所以添加任务十分方便。任务有两种执行方式:同步执行和异步执行,他们之间的区别是是否会创建新的线程。

同步(sync)和异步(async)的主要区别在于会不会阻塞当前线程,直到Block中的任务执行完毕!

如果是同步(sync)操作,它会阻塞当前线程并等待Block中的任务执行完毕,然后当前线程才会继续往下运行。

如果是异步(async)操作,当前线程会直接往下执行,它不会阻塞当前线程。

队列:用于存放任务。一共有两种队列,串行队列和并行队列。

串行队列:队列中的任务,GCD会FIFO(先进先出)地取出来一个,执行一个,然后取下一个,这样一个一个的执行。

并行队列:队列中的任务,GCD也会FIFO的取出来,但不同的是,它取出来一个就会放到别的线程,然后再取出来一个又放到另一个的线程。这样由于取的动作很快,忽略不计,看起来,所有的任务都是一起执行的。不过需要注意,GCD会根据系统资源控制并行的数量,所以如果任务很多,它并不会让所有任务同时执行。

1.0 创建和获得队列

1.1 主队列:这是一个特殊的串行队列。什么是主队列,大家都知道吧,它用于刷新UI,任何需要刷新UI的工作都要在主队列执行,所以一般耗时的任务都要放到别的线程执行。

dispatch_queue_t queue = dispatch_get_main_queue();

1.2 自己创建的队列:其中第一个参数是标识符,用于DEBUG的时候标识唯一的队列,可以为空。大家可以看xcode的文档查看参数意义。第二个参数用来表示创建的队列是串行的还是并行的,传入DISPATCH_QUEUE_SERIAL或NULL表示创建串行队列。传入DISPATCH_QUEUE_CONCURRENT表示创建并行队列。

串行队列

dispatch_queue_t queue1 = dispatch_queue_create("tk.bourne.testQueue", NULL);

dispatch_queue_t queue2 = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_SERIAL);

并行队列

dispatch_queue_t queue3 = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_CONCURRENT);

1.3 全局并行队列:只要是并行任务一般都加入到这个队列。这是系统提供的一个并发队列。

第一个参数线程优先级

第二个参数0 :占位符、目前没有意义

dispatch_queue_t queue4 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

1.4 Global Dispatch Queue(高优先级)的获取方法

dispatch_queue_t globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);

1.5 Global Dispatch Queue(默认优先级)的获取方法

dispatch_queue_t globalDispatchQueueDefault = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

1.6 Global Dispatch Queue(低优先级)的获取方法

dispatch_queue_t globalDispatchQueueLow = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);

1.7 Global Dispatch Queue(后台优先级)的获取方法

dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);

2.0 创建任务

同步任务:会阻塞当前线程(SYNC)

dispatch_sync(queue1, ^{

NSLog(@"%@", [NSThread currentThread]);

});

异步任务:不会阻塞当前线程(ASYNC)

dispatch_async(queue3, ^{

NSLog(@"%@", [NSThread currentThread]);

});

二、队列组

        队列组可以将很多队列添加到一个组里,这样做的好处是,当这个组里所有的任务都执行完了,队列组会通过一个方法通知我们。

1.创建队列组

dispatch_group_t group = dispatch_group_create();

2.创建队列

dispatch_queue_t queue  = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);

3.多次使用队列组的方法执行任务,只有异步方法

3.1.执行3次循环

dispatch_group_async(group, queue, ^{

for(NSIntegeri =0; i <3; i++) {

NSLog(@"group-01 - %@", [NSThreadcurrentThread]);

}

});

3.2.主队列执行8次循环

dispatch_group_async(group,dispatch_get_main_queue(), ^{

for(NSIntegeri =0; i <8; i++) {

NSLog(@"group-02 - %@", [NSThreadcurrentThread]);

}

});

3.3.执行5次循环

dispatch_group_async(group, queue, ^{

for(NSIntegeri =0; i <5; i++) {

NSLog(@"group-03 - %@", [NSThreadcurrentThread]);

}

});

4.都完成后会自动通知

dispatch_group_notify(group,dispatch_get_main_queue(), ^{

NSLog(@"完成- %@", [NSThreadcurrentThread]);

});

三、dispatch_barrier_sync

为了高效率地进行访问,读取处理追加到Concurrent Dispathc Queue中,写入处理在任一个读取处理没有执行的状态下,追加到Serial Dispatch Queue中即可(在写入处理结束之前,读取处理不可执行)。

虽然利用Dispatch Group和dispatch_set_target_queue函数也可实现,但是源码会很复杂。GCD为我们提供了高效的dispatch_barrier_sync函数。该函数同dispatch_queue_create函数生成的Concurrent Dispathc Queue一起使用。

dispatch_queue_tqueue =dispatch_queue_create("id=929",DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, ^{

[NSThreadsleepForTimeInterval:6];

NSLog(@"reading01");

});

dispatch_async(queue, ^{

[NSThreadsleepForTimeInterval:3];

NSLog(@"reading02");

});

dispatch_async(queue, ^{

[NSThreadsleepForTimeInterval:2];

NSLog(@"reading03");

});

dispatch_async(queue, ^{

[NSThreadsleepForTimeInterval:9];

NSLog(@"reading04");

});

#warning dispatch_barrier_async会监控queue直到先加入队列的任务执行完才会执行自己加入的任务,并且会阻塞queue直到自己block中的任务执行完才会让后续任务执行。dispatch_barrier_async函数本身是异步的不会阻塞当前线程

dispatch_barrier_async(queue, ^{

[NSThreadsleepForTimeInterval:5];

NSLog(@"writing");

});

NSLog(@"走到dispatch_barrier_async函数下面了");

dispatch_async(queue, ^{

NSLog(@"reading05");

});

dispatch_async(queue, ^{

NSLog(@"reading06");

});

dispatch_async(queue, ^{

NSLog(@"reading07");

});

四、dispatch_set_target_queue

/**

dispatch_queue_create函数生成的Dispatch Queue不管是Serial Dispatch Queue还是Concurrent Dispatch Queue,都使用与默认优先级Global Dispatch Queue相同执行优先级的线程。

*/

dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("id=19", DISPATCH_QUEUE_SERIAL);

dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0);

//变更优先级的函数dispatch_set_target_queue

/**

参数一:要变更优先级的Dispatch Queue

参数二:目标优先级Dispatch Queue,与目标Dispatch Queue有相同的执行优先级

*/

dispatch_set_target_queue(mySerialDispatchQueue, globalDispatchQueueBackground);

五、dispatch_after

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC);

/**

参数一:指定时间用的 dispatch_time_t 类型值

参数二:在哪个队列执行

参数三:要执行的任务

*/

dispatch_after(time, dispatch_get_main_queue(), ^{

NSLog(@"waited at least three seconds.");

});

#warning dispatch_after函数并不是指定延迟后执行block里的任务,而是指定时间后把任务加进队列

六、dispatch_apply

/**

dispatch_apply函数是dispatch_sync函数和Dispatch Group的关联API。该函数按指定的次数将指定的Block追加到指定的Dispatch Queue中,并等待全部处理执行结束。

*/

/**

参数一: 重复次数

参数二: 追加对象的Dispatch Queue

参数三: 追加的处理

与目前为止所出现的例子不同,第三个参数的Block为带有Block的参数。这是为了按第一个参数重复追加Block并区分各个Block而使用。

*/

//    dispatch_apply(10, queue, ^(size_t index) {

//        NSLog(@"%zu",index);

//    });

//#warning dispatch_apply会阻塞当前线程直到queue中新添加的10次任务执行结束才会取消阻塞。要想10次任务依次执行可以把queue换成一个串行队列

//    NSLog(@"done");

/* 例如要对NSArray类对象的所有元素执行处理时,不必一个一个编写for循环部分。*/

//    NSArray *array = [NSArray arrayWithObjects:@1,@2,@3,@4,@5,@6,@7,@8,@9,@10,@11, nil];

//    dispatch_apply([array count], queue, ^(size_t index) {

//        NSLog(@"%zu: %@", index, [array objectAtIndex:index]);

//    });

/* 由于dispatch_apply函数也与dispatch_sync函数相同,会等待处理执行结束,因此推荐在dispatch_async函数中非同步地执行dispatch_apply函数 */

NSArray *array = [NSArray arrayWithObjects:@1,@2,@3,@4,@5,@6,@7,@8,@9,@10,@11, nil];

dispatch_async(queue, ^{

//Global Dispatch Queue 等待dispatch_appply函数中全部处理执行结束

dispatch_apply([array count], queue, ^(size_t index) {

//并列处理包含在NSArray对象的全部对象

NSLog(@"%zu: %@", index, [array objectAtIndex:index]);

});

//dispatch_apply函数中的处理全部执行结束

/* 在Main Dispatch Queue中非同步执行 */

dispatch_async(dispatch_get_main_queue(), ^{

/*

在Main Dispatch Queue中执行处理

用户界面更新等

*/

NSLog(@"done");

});

});

七、dispatch_suspend / dispatch_resume

/**

当追加大量处理到Dispatch Queue时,在追加处理过程中,有时希望不执行已经追加的处理。例如演算结果被Block截获时,一些处理会对这个演算结果造成影响。

在这种情况下,只要挂起Dispatch Queue即可。当可以执行时再恢复。

*/

dispatch_queue_t queue1 = dispatch_queue_create("id = 88", DISPATCH_QUEUE_CONCURRENT);

//dispatch_suspend函数挂起指定的Dispatch Queue

dispatch_suspend(queue1);

//dispatch_resume函数恢复指定的Dispatch Queue

dispatch_resume(queue1);

#warning 这些函数对已经执行的处理没有影响。挂起后,追加到Dispatch Queue中尚未执行的处理在此之后停止执行。而恢复则使得这些处理能够继续执行。

八、Dispatch Semaphore

/**

当并行执行的处理更新数据时,会产生数据不一致的情况,有时应用程序还会异常结束。虽然使用Serial Dispatch Queue和dispatch_barrier_async函数可避免这类问题,但有必要进行更细粒度的排他控制。

*/

#pragma mark - 出错的代码

//    NSMutableArray *array = [[NSMutableArray alloc]init];

//    for (int i = 0; i < 100000; i++) {

//        dispatch_async(queue, ^{

//            [array addObject:[NSNumber numberWithInt:i]];

//        });

//    }

/*因为该源代码使用Global Dispatch Queue更新NSMutableArray类对象,所在执行后由于内存错误导致应用程序结束的概率很高。此时应使用Dispatch Semaphore。

Dispatch Semaphore是持有计数的信号,该计数是多线程编程中的计数类型信号。计数为0时等待,计数为1或大于1时,减1而不等待。

*/

//    //设置参数为1

//    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

//    //设置超是等待,即最长等待时间

//    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);

//    /**

//    参数一: dispatch_semaphore_t对象

//    参数二: 最长等待时间,与dispatch_group_wait中的超时等待是一样的

//    */

//    long result = dispatch_semaphore_wait(semaphore, time);

//    if (result == 0) {

//

//    }else{

//

//    }

/** 用Dispatch Semaphore改写出错的代码

生成Dispatch Semaphore

Dispatch Semaphore的计数初始值设定为1

保证可访问NSMutableArray类对象的线程同时只能有一个。

*/

dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

NSMutableArray *array = [[NSMutableArray alloc]init];

for (int i = 0; i < 100000; i++) {

dispatch_async(queue, ^{

/**

等待Dispatch Semaphore

一直等待,直到Dispatch Semaphore的计数值达到大于等于1

*/

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

/**

由于Dispatch Semaphore的计数值达到或大于等于1

所以将Dispatch Semaphore的计数值减去1

dispatch_semaphore_wait函数执行返回

即执行到此时的Dispatch Semaphore的计数恒为0

由于可访问NSMutableArray类对象的线程只有1个

因此可安全地进行更新

*/

[array addObject:[NSNumber numberWithInt:i]];

NSLog(@"%d",i);

/**

排他控制处理结束,

所以通过dispatch_semaphore_signal函数将Dispatch Semaphore的计数值加1

如果有通过dispatch_semaphore_wait函数

等待Dispatch Semaphore的计数值增加的线程,

就由最先等待的线程执行。

*/

dispatch_semaphore_signal(semaphore);

});

}

九、dispatch_once

/* dispatch_once函数是保证在应用程序执行中只执行一次指定处理的API。 */

static int initialized = NO;

if (initialized == NO) {

//初始化

initialized = YES;

}

#warning 上面的源代码在大多数情况下也是安全的,但是在多核CPU中,在正在更新表示是否初始化的标志变量时读取,就有可能多次执行初始化处理。而dispatch_once函数就没有这个问题,通过dispatch_once函数生成的代码即使在多线程环境下执行,也可保证百分百安全。

/* 上面的代码可以简化 */

static dispatch_once_t pred;

dispatch_once(&pred, ^{

//初始化

});

-----------------------我是无趣的分割线---------------------

上面这么枯躁的东西都能坚持看完,到此已经证明你有编程的资质!祝你编程之路一路顺风!!!

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

推荐阅读更多精彩内容

  • #import "ViewController.h" @interface ViewController () @...
    艾克12138阅读 211评论 0 0
  • 目录 一、基本概念1.多线程2.串行和并行, 并发3.队列与任务4.同步与异步5.线程状态6.多线程方案 二、GC...
    BohrIsLay阅读 1,557评论 5 12
  • 基于C接口的线程调度。 dispatch_queue_attr_t : 定义一个queue的时候,用于指定queu...
    lzh_coder阅读 937评论 0 0
  • 前言 GCD是苹果为多核的并行运算提出的解决方案,所以会自动合理地利用更多的CPU内核(比如双核、四核),最重要的...
    進无尽阅读 248评论 0 1
  • 一、简介在iOS所有实现多线程的方案中,GCD应该是最有魅力的,因为GCD本身是苹果公司为多核的并行运算提出的解决...
    MYS_iOS_8801阅读 568评论 0 0