012-GCD多线程技术

多线程

线程是进程内部执行任务的一种途径,多线程技术能适当提高程序执行效率和资源利用率,iOS 中的多线程技术主要有以下几种

  • GCD
  • NSOperation & NSOperationQueue
  • NSThread
  • Pthreads

多线程的创建是需要资源开销的,同时维护和调度线程也需要开销,程序设计和线程间通信也会因线程数目增多而变得复杂。
iOS 的主线程是在一个应用启动后默认开启的,主要负责显示、刷新和处理 UI,因此不能把比较耗时的任务放在主线程完成,会带来 UI 卡顿问题。
同时多线程也会带来线程安全的问题,当多个线程同时访问同一个对象或是存储空间时,由于读写操作的非原子性或是线程间的协同不够,就会带来严重的数据丢失或错乱问题,因此需要对资源进行同步加锁操作。

@synchronized(锁对象) { // 需要锁定的代码  };

当然在定义属性的时候也可以通过设置 atomic 特性来为属性的读写方法加锁。

GCD

GCD 是 iOS 用来管理多线程的技术,它会自动利用更多 CPU 内核,自动管理线程生命周期,使得使用线程变得更加轻便简洁。

任务和队列

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

  • 任务

在 GCD 中任务表现为一个个 block,包含需要执行的代码,任务的执行分两种,同步执行和异步执行,同步执行会阻塞当前线程,异步执行会创建新线程,不会阻塞当前线程。

dispatch_async 方法会异步执行任务,这意味着它不会阻塞当前线程

-(void)func{
    dispatch_async(qQueue, ^{
        NSLog(@"1");
    });
    NSLog(@"2");
}

这里 1 和 2 的打印顺序不固定,因为 async 执行任务不会阻塞,所以当前线程会继续向下执行。

dispatch_async 方法会同步执行任务,意味着当前线程一直阻塞到它完成任务才会继续执行

-(void)func{
    dispatch_sync(qQueue, ^{
        NSLog(@"1");
    });
    NSLog(@"2");
}

这里打印顺序一定是先 1 后 2 的。

  • 队列

队列用于存放任务,然后由 Run Loop 从中取出任务分发给各个线程。队列也分为串行队列和并行队列。串行队列会按照 FIFO 顺序被执行,并行队列则会并行执行,并行队列只能在异步函数中起作用。

创建队列

  • 主队列

主队列是一个特殊的串行队列,放在主队列的任务都会放到主线程执行

dispatch_queue_t queue = dispatch_get_main_queue();
  • 全局并行队列

全局并行队列是系统提供的并行队列,无需手动创建。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

这里第二个 flag 参数是保留位,使用时要置0,第一个参数表示线程优先级,也就是说有4个可选的全局并行队列,优先级分别是

#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
  • 串行队列
// 创建串行队列(队列类型传递NULL或者DISPATCH_QUEUE_SERIAL)
dispatch_queue_t queue = dispatch_queue_create("serial_queue", NULL);
  • 并行队列
dispatch_queue_t queue = dispatch_queue_create("concurrent.queue", DISPATCH_QUEUE_CONCURRENT);

自定义队列的优先级有两种定义方法

  • dispatch_queue_attr_make_with_qos_class 方法

    dispatch_queue_attr_t queue_attr = dispatch_queue_attr_make_with_qos_class (DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY,-1);
    dispatch_queue_t queue = dispatch_queue_create("queue", queue_attr);
    
  • dispatch_set_target_queue

      dispatch_queue_t queue = dispatch_queue_create("myqueue", NULL);
      dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
      dispatch_set_target_queue(queue, globalQueue);
      dispatch_async(queue, ^{
          NSLog(@"async");
          dispatch_async(dispatch_get_main_queue(), ^{
              NSLog(@"main");
          });
      });
    

这个方法还可以设置队列层次结构,当我们想让不同队列的任务同步执行时,可以创建一个串行队列,将其他队列的 target 设置为这个队列,就可以实现了。

    dispatch_queue_t targetQueue = dispatch_queue_create("myqueue", NULL);
    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_set_target_queue(targetQueue, globalQueue);

    dispatch_queue_t queue1 = dispatch_queue_create("queue1", NULL);
    dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);
    dispatch_set_target_queue(queue1, targetQueue);
    dispatch_set_target_queue(queue2, targetQueue);
    dispatch_async(queue1, ^{
        NSLog(@"queue1 1");
    });
    dispatch_async(targetQueue, ^{
        NSLog(@"async");
    });
    dispatch_async(queue2, ^{
        NSLog(@"queue2 1");
    });
    dispatch_async(queue2, ^{
        NSLog(@"queue2 2");
    });
    dispatch_async(queue2, ^{
        NSLog(@"queue2 3");
    });
    dispatch_async(queue1, ^{
        NSLog(@"queue1 2");
    });
    dispatch_async(queue1, ^{
        sleep(1);
        NSLog(@"queue1 3");
    });

这样执行的结果如下

queue1 1
queue1 2
queue1 3
async
queue2 1
queue2 2
queue2 3

简单来说,设置了 target 以后,并不是按照设置的队列顺序来执行任务的,而是按照分发任务的队列顺序来执行,如果先设置了 queue1 的任务,就会将 queue1 的任务执行完再执行其他队列。

分发任务

dispatch_async

前面提到过,这个方法是异步执行代码块中任务。

dispatch_sync

这个方法会同步执行任务,执行顺序可以预测,但是要注意使用不当可能会造成死锁。

dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"123");
    });
NSLog(@"456");

在这里,dispatch_sync 由于是在主线程运行,因此会阻塞主线程,一直等到 block 中的代码执行返回后才会继续执行,但是 block 又是在主线程执行的,因为主线程被阻塞所以不会执行 block,于是两者相互等待,就发生了死锁。

once

dispatch_once 保证 block 只会被执行一次,一般用于单例模式中初始化 static 的单例对象。

    static dispatch_once_t onceToken;
    NSLog(@"%ld", onceToken);
    dispatch_once(&onceToken, ^{
        
    });
    NSLog(@"%ld", onceToken);

打印结果可以看到 onceToken 一开始是 0,执行完以后会变成 -1,从而标识这个 block 已被执行过。

apply

dispatch_apply 这个方法会循环执行任务,指定循环次数就会在 queue 中将 block 循环执行指定次数。

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_apply(5, queue, ^(size_t i) {
        if (i == 3)
        {
            sleep(1);
        }
        NSLog(@"%zu", i);
    });

当然至于是串行执行还是并行执行则要看队列的属性。

group

group 是一组执行的任务,适用于需要等待一组任务完成触发其他代码的场景。

  • 创建一个 group 对象

    dispatch_group_t group = dispatch_group_create();
    
  • 插入到队列中

    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    
    });
    
  • 同步等待所有任务完成后继续执行后面的代码

    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    

    这里 timeout 可以设置为 FOREVER,也可以设置为 dispatch_time_t 类型的参数

    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)1 * NSEC_PER_SEC);
      dispatch_group_wait(group, time);
    
  • 异步等待所有任务完成后执行

        dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
          NSLog(@"finish");
      });
    
  • enter 和 leave

    也可以使用 dispatch_group_enter 和 dispatch_group_leave 方法实现将任务加入到 group 中的操作

        dispatch_group_t group = dispatch_group_create();
      dispatch_group_enter(group);
      dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
      dispatch_async(queue, ^{
          NSLog(@"group in");
          sleep(1);
          dispatch_group_leave(group);
      });
      NSLog(@"main");
    

    这里效果与 dispatch_group_wait 相同。

after

dispatch_after 会在指定时间后将任务加入到指定队列中,当然不代表会立刻执行此任务。

    dispatch_queue_t queue = dispatch_queue_create("queue", NULL);
    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t) 3 * NSEC_PER_SEC);
    dispatch_after(time, queue, ^{
        NSLog(@"after 3 second");
    });

barrier

dispatch_barrier_async 用于等待前面的任务执行完毕后自己才执行,而它后面的任务需等待它完成之后才执行。还有个串行函数 dispatch_barrier_sync 是会阻塞当前线程等待指定队列完成任务再继续执行的。

    dispatch_queue_t queue1 = dispatch_queue_create("queue1", NULL);
    dispatch_queue_t queue2 = dispatch_queue_create("queue2", NULL);
    
    dispatch_async(queue1, ^{
        NSLog(@"1 1");
    });
    
    dispatch_async(queue1, ^{
        sleep(1);
        NSLog(@"1 2");
    });
    
    dispatch_barrier_sync(queue1, ^{
        NSLog(@"1 3");
    });
    
    dispatch_async(queue2, ^{
        NSLog(@"2 1");
    });

打印结果是

1 1
1 2
1 3
2 1

如果是执行的 dispatch_barrier_async 则 "2 1" 不会最后才打印。

block

可以看到插入到队列的任务一般就是 block 代码块中的代码,也可以自己定义一个 block 进行复用。

    dispatch_queue_t queue1 = dispatch_queue_create("queue1", NULL);
    dispatch_queue_t queue2 = dispatch_queue_create("queue2", NULL);
    
    dispatch_block_t block1 = dispatch_block_create(0, ^{
        NSLog(@"block1");
    });
    
    dispatch_block_t block2 = dispatch_block_create(0, ^{
        NSLog(@"block2");
    });
    
    dispatch_async(queue1, block1);
    dispatch_async(queue2, block2);
  • dispatch_block_cancel

    这个函数可以取消 block 的执行,例如

        dispatch_queue_t queue1 = dispatch_queue_create("queue1", NULL);
      dispatch_queue_t queue2 = dispatch_queue_create("queue2", NULL);
      
      dispatch_block_t block1 = dispatch_block_create(0, ^{
          NSLog(@"block1");
      });
      
      dispatch_block_t block2 = dispatch_block_create(0, ^{
          NSLog(@"block2");
      });
      
      dispatch_async(queue1, block1);
      dispatch_async(queue2, block2);
      dispatch_block_cancel(block1);
    

    这样将只会打印 "block2"。

  • dispatch_block_wait

    这个函数会阻塞当前线程,并等待前面的任务执行完毕。

        dispatch_queue_t queue1 = dispatch_queue_create("queue1", NULL);
      
      dispatch_block_t block1 = dispatch_block_create(0, ^{
          sleep(1);
          NSLog(@"block1");
      });
      
      dispatch_async(queue1, block1);
      dispatch_block_wait(block1, DISPATCH_TIME_FOREVER);
      NSLog(@"continue");
    

    最终打印结果是

    block1
    continue
    
  • dispatch_block_notify

    dispatch_block_notify 不会阻塞当前线程,会在指定的 block 执行结束后将指定 block 插入到指定的 queue 中。

        dispatch_queue_t queue1 = dispatch_queue_create("queue1", NULL);
      dispatch_queue_t queue2 = dispatch_queue_create("queue2", NULL);
      dispatch_block_t block1 = dispatch_block_create(0, ^{
          sleep(1);
          NSLog(@"block1");
      });
      dispatch_block_t block2 = dispatch_block_create(0, ^{
          NSLog(@"block2");
      });
      dispatch_async(queue1, block1);
      dispatch_block_notify(block1, queue2, block2);
      NSLog(@"continue");
    

    打印结果为

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

推荐阅读更多精彩内容