[iOS]多线程--GCD

关于GCD网上很多介绍, 大部分人都很熟悉, 这里只是本人学习的一个总结, 不到之处,还请指正.

一. 准备

在这之前, 我们需要明白几个比较容易混淆的概念:同步, 异步, 并发, 串行;

  • 同步和异步决定了要不要开启新的线程
    1.同步: 在当前线程中执行任务, 不具备开启新线程的能力
    2.异步: 在新的线程中执行任务, 具备开启新线程的能

  • 并发和串行决定了任务的执行方式
    1.并发: 多个任务并发(同时)执行
    2.串行: 一个任务执行完毕后, 再执行下一个任务(顺序执行)

明白了这几个概念, 再去看GCD相关的一些内容就会比较清晰了.

二. CGD中的队列

1. 串行队列

GCD中获取串行队列有两种途径:
第一种是使用dispatch_queue_create函数创建串行队列:

dispatch_queue_t  dispatch_queue_create(const char *label,  dispatch_queue_attr_t attr);

参数:

  • label: 队列名称, 是一个C的字符串, 可自定义
  • attr: 队列类型, 如果传NULL, 就是串行队列; 传DISPATCH_QUEUE_CONCURRENT,可创建并行队列

例如:

//创建串行队列
    dispatch_queue_t  queue= dispatch_queue_create("串行队列名称", NULL);
// 非ARC需要释放手动创建的队列, 现在一般用不到
 dispatch_release(queue); 

第二种就是直接使用主队列(跟主线程相关联的队列)
主队列是GCD自带的一种特殊的串行队列, 放在主队列中的任务,都会放到主线程中执行, 使用dispatch_get_main_queue()获得主队列;
例如:

// 获取主队列(串行队列)
dispatch_queue_t queue = dispatch_get_main_queue();
2. 并行队列

并行队列的获取也有两种方式, 第一种上面也有提到, 就是使用 dispatch_queue_create 来创建, 主要第二个参数:

dispatch_queue_t queue = dispatch_queue_create("并发队列",  DISPATCH_QUEUE_CONCURRENT);

但是, 我们一般不使用这个方式来获取并行队列, 而是使用系统预定义的全局并发队列;
使用dispatch_get_global_queue函数获得全局的并发队列:

 dispatch_queue_t dispatch_get_global_queue(long identifier, unsigned long flags);;

参数:

  • identifier: 队列的优先级, 系统预定义了四种, 以供开发者使用
//全局并发队列的优先级
 #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 // 后台
  • flags: 这个参数是预留给以后使用的, 暂时用不上, 传 0 即可;

例如:

 // 获得默认优先级的全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
3. 同步, 异步

同步异步的开启主要是使用下面的函数:

void
dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);

同步执行函数, 参数:

  • queue: 在哪个队列执行
  • block: 执行的任务
void
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

异步执行函数, 参数:

  • queue: 在哪个队列执行
  • block: 执行的任务

三. 同步, 异步, 串行, 并发组合测试

下面就来通过实例看一下, 同步, 异步, 串行, 并发直接的一些联系和区别;

测试一: 用同步函数往串行队列中添加任务

不会开启新的线程:

NSLog(@"用同步函数往串行队列中添加任务");
    //打印主线程
     NSLog(@"主线程----%@",[NSThread mainThread]);

     //创建串行队列
    dispatch_queue_t  queue= dispatch_queue_create("LZQueueName", NULL);

     //2.添加任务到队列中执行
     dispatch_sync(queue, ^{
        NSLog(@"下载图片1----%@",[NSThread currentThread]);
         sleep(3);
    });
    dispatch_sync(queue, ^{
          NSLog(@"下载图片2----%@",[NSThread currentThread]);
        sleep(2);
     });
    dispatch_sync(queue, ^{
        NSLog(@"下载图片3----%@",[NSThread currentThread]);
     });

输出:

2017-01-06 10:11:32.135 LZGCDTest[2938:57629] 用同步函数往串行队列中添加任务
2017-01-06 10:11:32.136 LZGCDTest[2938:57629] 主线程----<NSThread: 0x608000066240>{number = 1, name = main}
2017-01-06 10:11:32.136 LZGCDTest[2938:57629] 下载图片1----<NSThread: 0x608000066240>{number = 1, name = main}
2017-01-06 10:11:35.209 LZGCDTest[2938:57629] 下载图片2----<NSThread: 0x608000066240>{number = 1, name = main}
2017-01-06 10:11:37.280 LZGCDTest[2938:57629] 下载图片3----<NSThread: 0x608000066240>{number = 1, name = main}

可以看到, 是按照任务的添加顺序执行的, 而且是在当前线程(主线程)中执行的, 没有开启新线程(同步函数不具备开启新线程的能力);

测试二: 用同步函数往并发队列中添加任务

不会开启新的线程((同步函数不具备开启新线程的能力)),并发队列失去了并发的功能:

//打印主线程
     NSLog(@"主线程----%@",[NSThread mainThread]);

     //获取并行队列
     dispatch_queue_t  queue= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);


    // 添加任务到队列中执行
     dispatch_sync(queue, ^{
         NSLog(@"下载图片1----%@",[NSThread currentThread]);
         sleep(3);
     });
    dispatch_sync(queue, ^{
         NSLog(@"下载图片2----%@",[NSThread currentThread]);
        sleep(2);
     });
     dispatch_sync(queue, ^{
        NSLog(@"下载图片3----%@",[NSThread currentThread]);
     });

输出:

2017-01-06 10:08:37.874 LZGCDTest[2859:55553] 主线程----<NSThread: 0x60800007adc0>{number = 1, name = main}
2017-01-06 10:08:42.892 LZGCDTest[2859:55553] 下载图片1----<NSThread: 0x60800007adc0>{number = 1, name = main}
2017-01-06 10:08:45.966 LZGCDTest[2859:55553] 下载图片2----<NSThread: 0x60800007adc0>{number = 1, name = main}
2017-01-06 10:08:48.035 LZGCDTest[2859:55553] 下载图片3----<NSThread: 0x60800007adc0>{number = 1, name = main}

可以看出, 虽然使用的是并发队列, 但是使用的是同步函数, 由于同步函数没有开启新线程的能力, 所以并发队列就失去了并发性, 按照任务的添加顺序, 顺序执行;

测试三. 用异步函数往串行队列中添加任务

会开启线程,但是只开启一个线程:

 //打印主线程
     NSLog(@"主线程----%@",[NSThread mainThread]);

     //创建串行队列,iOS4.3之后:第二个参数可写  DISPATCH_QUEUE_SERIAL,之前只能NULL
     dispatch_queue_t  queue= dispatch_queue_create("LZQueueName", NULL);

     //2.添加任务到队列中执行
     dispatch_async(queue, ^{
         
        NSLog(@"下载图片1----%@",[NSThread currentThread]);
         sleep(3);
     });
     dispatch_async(queue, ^{
         
        NSLog(@"下载图片2----%@",[NSThread currentThread]);
         sleep(2);
     });
    dispatch_async(queue, ^{
         NSLog(@"下载图片3----%@",[NSThread currentThread]);
     });

输出:

2017-01-06 10:14:28.944 LZGCDTest[3019:60109] 主线程----<NSThread: 0x608000065440>{number = 1, name = main}
2017-01-06 10:14:33.331 LZGCDTest[3019:60150] 下载图片1----<NSThread: 0x60000006f2c0>{number = 3, name = (null)}
2017-01-06 10:14:36.404 LZGCDTest[3019:60150] 下载图片2----<NSThread: 0x60000006f2c0>{number = 3, name = (null)}
2017-01-06 10:14:38.478 LZGCDTest[3019:60150] 下载图片3----<NSThread: 0x60000006f2c0>{number = 3, name = (null)}

可以看到, 任务没有在主线程里执行, 开启了一个新的线程, 虽然是异步, 但是队列是串行队列, 所以任务还是按照添加的顺序, 顺序执行的;

测试四. 用异步函数往并发队列中添加任务

同时开启多个子线程执行任务:

//打印主线程
    NSLog(@"主线程----%@",[NSThread mainThread]);
    
    //1.获得全局的并发队列
    dispatch_queue_t queue =  dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    //2.添加任务到队列中,就可以执行任务
        //异步函数:具备开启新线程的能力
        dispatch_async(queue, ^{
                NSLog(@"下载图片1----%@",[NSThread currentThread]);
            sleep(3);
            });
        dispatch_async(queue, ^{
                NSLog(@"下载图片2----%@",[NSThread currentThread]);
            sleep(2);
            });
        dispatch_async(queue, ^{
                NSLog(@"下载图片2----%@",[NSThread currentThread]);
            });

输出:

2017-01-06 10:18:12.210 LZGCDTest[3134:63076] 主线程----<NSThread: 0x60800006e700>{number = 1, name = main}
2017-01-06 10:18:12.211 LZGCDTest[3134:63122] 下载图片1----<NSThread: 0x60000007c040>{number = 3, name = (null)}
2017-01-06 10:18:12.211 LZGCDTest[3134:63121] 下载图片2----<NSThread: 0x6080002671c0>{number = 4, name = (null)}
2017-01-06 10:18:12.211 LZGCDTest[3134:63124] 下载图片2----<NSThread: 0x60800026fe80>{number = 5, name = (null)}

可以看出, 这里开启了三个子线程来执行任务.
以上四个测试, 能够看出同步, 异步, 串行, 并行之间的关系, 只有异步函数和并行队列组合才能真正实现并发的效果.

测试五. 控制最大并发数

在进行并发操作的时候, 如果任务过多, 开启很多线程, 会导致APP卡死. 所以, 我们要控制最大并发数, 这就用到了信号量, 与之相关的三个函数为:

// value : 必须是大于等于0的数, 否则会返回NULL
dispatch_semaphore_t
dispatch_semaphore_create(long value);

long
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);

long
dispatch_semaphore_signal(dispatch_semaphore_t dsema);

这里有一篇文章是介绍信号量的, 可参考:关于dispatch_semaphore的使用
一个应用示例:

dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    // 设置最大任务数
dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);

    for (int i = 0; i < 100; i++) {
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        dispatch_group_async(group, queue, ^{
           // 执行任务
            NSLog(@"%d", i);
            // 模拟任务时间, 休眠2s
            sleep(2);
            
            dispatch_semaphore_signal(semaphore);
        });
    }
    
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

这样就会, 每十个一组的进行输出...

四. 一些应用

下面给出一些, GCD在编程中的常用示例:

1. 只执行一次
static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
    });

这个同常用来创建单例, 能够保证block内的代码只执行一次.

2. 延迟执行
dispatch_time_t time = dispatch_time ( DISPATCH_TIME_NOW , 3ull * NSEC_PER_SEC ) ;
    
    dispatch_after ( time , dispatch_get_main_queue (),^{
        
        NSLog ( @"waited at least three seconds." );
    });

这里是延迟3s来执行block内的任务;

3. 重复执行
dispatch_apply(5, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t index) {
        NSLog(@"重复执行5次");
    });

这个方法可以重复执行某个任务, 这里执行5次输出;

4. 指定执行

如果, 你想某个任务在其他任务执行之后再执行, 或者必须某个任务执行完,才能执行下面的任务, 可以使用这个函数dispatch_barrier_async :

dispatch_queue_t queue = dispatch_queue_create("LZQueueName", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"dispatch_async1");
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:4];
        NSLog(@"dispatch_async2");
    });
    
    dispatch_barrier_async(queue, ^{
        NSLog(@"dispatch_barrier_async");
        [NSThread sleepForTimeInterval:4];
        
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"dispatch_async3");   
    });

这里是使用的并发队列异步执行, 按说应该是互不影响的, 但是有了** dispatch_barrier_async** ,他的作用就是在他前面的任务执行结束后它才执行,而且它后面的任务等它执行完成之后才会执行.
输出:

2017-01-06 10:33:03.063 LZGCDTest[3454:71023] dispatch_async1
2017-01-06 10:33:05.004 LZGCDTest[3454:71026] dispatch_async2
2017-01-06 10:33:05.004 LZGCDTest[3454:71026] dispatch_barrier_async
2017-01-06 10:33:10.151 LZGCDTest[3454:71026] dispatch_async3
5. 汇总dispatch_group_async

dispatch_group_async可以实现监听一组任务是否完成,完成后得到通知执行其他的操作。比如你执行三个下载任务,当三个任务都下载完成后你才通知界面做相应的刷新操作:

// 获取全局队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    // 创建一个组
    dispatch_group_t group = dispatch_group_create();
    // 添加任务
    dispatch_group_async(group, queue, ^{
        [NSThread sleepForTimeInterval:3];
        NSLog(@"group1");
    });
    dispatch_group_async(group, queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"group2");
    });
    dispatch_group_async(group, queue, ^{
        [NSThread sleepForTimeInterval:3];
        NSLog(@"group3");
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"updateUi");
    });

输出:

2017-01-06 10:41:02.099 LZGCDTest[3668:76419] group2
2017-01-06 10:41:03.098 LZGCDTest[3668:76416] group3
2017-01-06 10:41:03.098 LZGCDTest[3668:76417] group1
2017-01-06 10:41:03.099 LZGCDTest[3668:76375] updateUi
6. 延长后台运行时间

当用户使用Home键使APP后台运行, 一般只有最多5s的时间, 就会被系统杀死, 如果在这些时间里你不能完成一些操作, 例如清理缓存, 保存数据, 就有可能造成数据丢失, 这时可以使用这个方法, 来使程序的后台运行时间延长至10分钟左右:

// AppDelegate.h文件
@property (assign, nonatomic) UIBackgroundTaskIdentifier backgroundUpdateTask;

// AppDelegate.m文件
- (void)applicationDidEnterBackground:(UIApplication *)application
{
    [self beingBackgroundUpdateTask];
    // 在这里加上你需要长久运行的代码
    [self endBackgroundUpdateTask];
}

- (void)beingBackgroundUpdateTask
{
    self.backgroundUpdateTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
        [self endBackgroundUpdateTask];
    }];
}

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

推荐阅读更多精彩内容