美文网首页
012-GCD多线程技术

012-GCD多线程技术

作者: Yasic | 来源:发表于2017-11-26 19:22 被阅读19次

多线程

线程是进程内部执行任务的一种途径,多线程技术能适当提高程序执行效率和资源利用率,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
    

相关文章

  • 012-GCD多线程技术

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

  • 多线程

    多线程技术 多线程技术2 多线程应用 server client join函数

  • Day18-多线程

    1.多线程技术1 2.多线程技术2 3.多线程技术 4.join函数

  • Python-day-18多线程

    1、多线程技术1 二、多线程技术2 三、多线程应用 四、jion函数

  • day18-多线程

    recode 一、多线程技术1 二、多线程技术2 三、client 多线程应用 四、join函数 耗时操作

  • iOS 多线程技术最牛图解

    了解更多 iOS 多线程技术,点击阅读以下文章 iOS 多线程技术深度解析 最浅显易懂的iOS多线程技术 - GC...

  • iOS基础知识整理之多线程技术

    多线程技术 多线程(multithreading) 是指软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的...

  • Day18 多线程

    01.多线程技术1 02.多线程技术2 03.多线程应用 04.join 函数 耗时操作 1.耗时操作放到主线程中...

  • 多线程整理

    多线程概念 先通过一幅图来了解一下多线程 多线程技术对比

  • 多线程

    1.多线程技术01

网友评论

      本文标题:012-GCD多线程技术

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