iOS多线程: GCD

系列文章:

多线程

多线程 pthread、NSThread

多线程 GCD

多线程 NSOperation

多线程运用

GCD

全称是Grand Central Dispatch,“伟大的中枢调度器”。GCD是苹果为多核的并行运算提出的解决方案,所以会自动合理地利用更多的CPU内核(比如双核、四核),最重要的是它会自动管理线程的生命周期(创建线程、调度任务、销毁线程),程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码。同时它使用的也是 c语言,提供了非常多强大的函数, 由于使用了 Block(Swift里叫做闭包),使得使用起来更加方便,而且灵活。所以基本上大家都使用 GCD 这套方案。

任务和队列

GCD中有2个核心概念

任务:执行什么操作

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

    同步:只是在当前线程中执行任务,不具备开启新线程的能力

    异步:可以在新的线程中执行任务,具备开启新线程的能力

  • 同步在当前线程中执行,任务会立即执行,它会阻塞当前线程并等待 Block 中的任务执行完毕,然后当前线程才会继续运行,就是在发出一个功能调用时,在没有得到结果之前,该调用就不继续往下运行(调用)。是一定不会开新线程的,也就是必须一件一件事做,等前一件做完了才能做下一件事。(一个线程,当前线程。);

  • 异步:可以开新线程,可以在新线程内执行任务,并不意味着一定就会开新线程;任务不会立即执行,当前线程会直接往下执行,它不会阻塞当前线程。当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的任务在完成后,通过状态、通知和回调来通知调用者。(多个线程,开辟出来的新线程。);

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

队列:用来存放任务

  • 队列:用来存放任务

  • 一共有两种队列, 串行队列并行队列

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

    放到并行队列的任务,根据同步或异步有不同的执行方式. GCD 也会 FIFO的取出来,但不同的是,它取出来一个就会放到别的线程,然后再取出来一个又放到另一个的线程。这样由于取的动作很快,忽略不计,看起来,所有的任务都是一起执行的。不过需要注意,GCD 会根据系统资源控制并行的数量,所以如果任务很多,它并不会让所有任务同时执行。有高、默认、低和后台4个优先级。并发功能只有在异步(dispatch_async)函数下才有效.

1、并发队列(Concurrent Dispatch Queue)
可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)
并发功能只有在异步(dispatch_async)函数下才有效

2、串行队列(Serial Dispatch Queue)
让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)

同步执行 异步执行
串行队列 当前线程,一个一个执行 其他线程,一个一个执行
并行队列 当前线程,一个一个执行 开很多线程,一起执行
并行队列 手动创建串行队列 主队列
同步执行 当前线程,一个一个执行 当前线程,一个一个执行 没有开启新线程, 一个一个执行
异步执行 开很多线程,一起执行 其他线程,一个一个执行 没有开启新线程, 一个一个执行
image.png

GCD使用步骤

  1. 定制任务:确定想做的事情
  2. 将任务添加到队列中:GCD会自动将队列中的任务取出,放到对应的线程中执行。Tips:任务的取出遵循队列的FIFO原则:先进先出,后进后出

创建任务

queue:队列

block:任务

  • 同步任务: 会阻塞当前线程 (SYNC),不具备开启新线程的能力
    dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
// OBJECTIVE-C
  dispatch_sync(<#queue#>, ^{
      //code here
      NSLog(@"%@", [NSThread currentThread]);
  });

// SWIFT
  dispatch_sync(<#queue#>, { () -> Void in
      //code here
      println(NSThread.currentThread())
  })
  • 异步任务:不会阻塞当前线程 (ASYNC),具备开启新线程的能力
    dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
OBJECTIVE-C
  dispatch_async(<#queue#>, ^{
      //code here
      NSLog(@"%@", [NSThread currentThread]);
  });

SWIFT
  dispatch_async(<#queue#>, { () -> Void in
      //code here
      println(NSThread.currentThread())
  })

GCD中还有个用来执行任务的函数(栅栏-dispatch_barrier) 在前面的任务执行结束后它才执行,而且它后面的任务等它执行完成之后才会执行, 后面会讲到

创建队列

  • 1、使用dispatch_get_main_queue()获取主队列

    主队列:(跟主线程相关联的队列)是GCD自带的一种特殊的 串行队列。放在主队列中的任务,都会放到主线程中执行。任何需要刷新 UI 的工作都要在主队列执行,所以一般耗时的任务都要放到别的线程执行

      //OBJECTIVE-C
  dispatch_queue_t queue = dispatch_get_main_queue();

  //SWIFT
  let queue = dispatch_get_main_queue()

特点:
a、主队列是与主线程相关联的队列
b、主队列是GCD自带的一种特殊的串行队列
c、放在主队列中的任务,都会放到主线程中执行

  • 2、使用dispatch_queue_create函数创建队列 (串行队列, 并行队列)

dispatch_queue_create(const char *label, dispatch_queue_attr_t attr)

label:队列名称

attr:队列属性

DISPATCH_QUEUE_SERIAL 或NULL:串行队列

DISPATCH_QUEUE_CONCURRENT:并发队列

//OBJECTIVE-C
  //串行队列
  dispatch_queue_t queue = dispatch_queue_create("serial_queue", NULL);
  dispatch_queue_t queue = dispatch_queue_create("serial_queue", DISPATCH_QUEUE_SERIAL);
  //并行队列
  dispatch_queue_t queue = dispatch_queue_create("concurrent.queue", DISPATCH_QUEUE_CONCURRENT);

  //SWIFT
  //串行队列
  let queue = dispatch_queue_create("tk.bourne.testQueue", nil);
  let queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_SERIAL)
  //并行队列
  let queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_CONCURRENT)
  • 3、使用dispatch_get_global_queue获取全局并发队列

dispatch_get_global_queue(long identifier, unsigned long flags);
identifier: 队列的优先级
flags:此参数暂时无用,用0即可
全局并发队列的优先级

#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 // 后台
  dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

  //SWIFT
  let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)

特点:GCD默认已经提供了全局的并发队列,供整个应用使用,不需要手动创建

内存管理补充:
如果你部署的最低目标低于 iOS 6.0 or Mac OS X 10.8
你应该自己管理GCD对象,使用(dispatch_retain,dispatch_release),ARC并不会去管理它们
如果你部署的最低目标是 iOS 6.0 or Mac OS X 10.8 或者更高的
ARC已经能够管理GCD对象了,这时候,GCD对象就如同普通的OC对象一样,不应该使用dispatch_retain or dispatch_release

栅栏-dispatch_barrier

    Swift
    func dispatch_barrier_async(_ queue: dispatch_queue_t, _ block: dispatch_block_t):
    OC: 
    dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);

这个方法重点是你传入的 queue,当你传入的 queue 是通过 DISPATCH_QUEUE_CONCURRENT 参数自己创建的 queue 时,这个方法会阻塞这个 queue(注意是阻塞 queue ,而不是阻塞当前线程),一直等到这个 queue 中排在它前面的任务都执行完成后才会开始执行自己,自己执行完毕后,再会取消阻塞,使这个 queue 中排在它后面的任务继续执行。

如果你传入的是其他的 queue, 那么它就和 dispatch_async 一样了。

func dispatch_barrier_sync(_ queue: dispatch_queue_t, _ block: dispatch_block_t):

这个方法的使用和上一个一样,传入 自定义的并发队列 DISPATCH_QUEUE_CONCURRENT,它和上一个方法一样的阻塞 queue,不同的是 这个方法还会 阻塞当前线程
如果你传入的是其他的 queue, 那么它就和 dispatch_sync 一样了。

特别注意:使用sync函数往当前串行队列中添加任务,会卡住当前的串行队列 (线程卡死)

4个术语比较容易混淆:同步、异步、并发、串行

1.同步和异步主要影响:能不能开启新的线程

同步:只是在当前线程中执行任务,不具备开启新线程的能力

异步:可以在新的线程中执行任务,具备开启新线程的能力

2.并发和串行主要影响:任务的执行方式

并发:多个任务并发(同时)执行

串行:一个任务执行完毕后,再执行下一个任务

GCD运用

一、线程间通信

在1个进程中,线程往往不是孤立存在的,多个线程之间需要经常进行通信
例如在子线程下载图片,在主线程刷新UI显示图片。

1、调用NSObject的方法

  • 在主线程上执行操作
    - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;

  • 在指定线程上执行操作
    - (void)performSelector:(SEL)aSelector onThread:(NSThread*)thr withObject:(id)arg waitUnti

2、GCD方法:

//从子线程回到主线程
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
        // 执行耗时的异步操作...
        dispatch_async(dispatch_get_main_queue(), ^{
            // 回到主线程,执行UI刷新操作});
            
        });

二、延时执行

iOS常见的延时执行有2种方式

1、调用NSObject的方法

[self performSelector:@selector(run) withObject:nil afterDelay:2.0];
// 2秒后调用self的run方法

2、使用GCD函数

延迟执行, 这段代码将会在2秒后将任务插入RunLoop当中
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,(int64_t)(2.0* NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 2秒后异步执行这里的代码...
});

三、一次性代码

// 使用dispatch_once函数能保证某段代码在程序运行过程中只被执行1次
单例的时候会使用到,避免返回的对象不是同一个对象
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只执行1次的代码(这里面默认是线程安全的):不会有其他线程可以访问到这里
});

单例模式

作用:
可以保证在程序运行过程,一个类只有一个实例,而且该实例易于供外界访问。从而方便地控制了实例个数,并节约系统资源

使用场合:
在整个应用程序中,共享一份资源(这份资源只需要创建初始化1次)

static HLUserManager *_singleton = nil;
@implementation HLUserManager

+ (instancetype)sharedManager {
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _singleton = [[HLUserManager alloc] init];
    });
    return _singleton;
}

四、快速迭代

dispatch_apply类似一个for循环,并发的执行每一项。所有任务结束后,dispatch_apply才会返回,会阻塞当前线程。如果传入队列是串行队列,要注意防止死锁现象的发生。
循环执行任务,任务的顺序是无序列的并且会堵塞当前的线程。

    // 使用dispatch_apply函数能进行快速迭代遍历
    // 第一个参数: 迭代次数,第二个参数: 线程队列(并发队列) ,第三个参数: index 索引 block: 任务
    dispatch_apply(10, dispatch_get_global_queue(0,0), ^(size_t index){
        // 执行10次代码,index顺序不确定
               NSLog(@"GCD- %zd -- %@", index, [NSThread currentThread]); 
    });

五、队列组

   分别异步执行2个耗时的操作、2个异步操作都执行完毕后,再回到主线程执行操作
    // 1.实例化一个调度组
    dispatch_group_t group = dispatch_group_create();
    // 2.获取一个全局队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    // 3.把任务添加进队列中

    dispatch_group_async(group, queue, ^{
        // 执行1个耗时的异步操作
        
    });
    dispatch_group_async(group, queue, ^{
        // 执行1个耗时的异步操作
        
    });
       // 4.获得所有调度任务都完成后的通知 
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 等前面的异步操作都执行完毕后,回到主线程...
               NSLog(@"更新UI");
 
    });

手动管理group关联的block的运行状态(或计数),进入和退出group次数必须匹配

dispatch_group_enter(group);

dispatch_group_leave(group);

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

SWIFT
//1.创建队列组
let group = dispatch_group_create()
//2.创建队列
let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)

//3.多次使用队列组的方法执行任务, 只有异步方法
//3.1.执行3次循环
dispatch_group_async(group, queue) { () -> Void in
    for _ in 0..<3 {
        NSLog("group-01 - %@", NSThread.currentThread())
    }
}

//3.2.主队列执行8次循环
dispatch_group_async(group, dispatch_get_main_queue()) { () -> Void in
    for _ in 0..<8 {
        NSLog("group-02 - %@", NSThread.currentThread())
    }
}

//3.3.执行5次循环
dispatch_group_async(group, queue) { () -> Void in
    for _ in 0..<5 {
        NSLog("group-03 - %@", NSThread.currentThread())
    }
}

//4.都完成后会自动通知
dispatch_group_notify(group, dispatch_get_main_queue()) { () -> Void in
    NSLog("完成 - %@", NSThread.currentThread())
}

打印结果

2015-07-28 03:40:34.277 test[12540:3319271] group-03 - <NSThread: 0x7f9772536f00>{number = 3, name = (null)}
2015-07-28 03:40:34.277 test[12540:3319146] group-02 - <NSThread: 0x7f977240ba60>{number = 1, name = main}
2015-07-28 03:40:34.277 test[12540:3319146] group-02 - <NSThread: 0x7f977240ba60>{number = 1, name = main}
2015-07-28 03:40:34.277 test[12540:3319271] group-03 - <NSThread: 0x7f9772536f00>{number = 3, name = (null)}
2015-07-28 03:40:34.278 test[12540:3319146] group-02 - <NSThread: 0x7f977240ba60>{number = 1, name = main}
2015-07-28 03:40:34.278 test[12540:3319271] group-03 - <NSThread: 0x7f9772536f00>{number = 3, name = (null)}
2015-07-28 03:40:34.278 test[12540:3319271] group-03 - <NSThread: 0x7f9772536f00>{number = 3, name = (null)}
2015-07-28 03:40:34.278 test[12540:3319146] group-02 - <NSThread: 0x7f977240ba60>{number = 1, name = main}
2015-07-28 03:40:34.277 test[12540:3319273] group-01 - <NSThread: 0x7f977272e8d0>{number = 2, name = (null)}
2015-07-28 03:40:34.278 test[12540:3319271] group-03 - <NSThread: 0x7f9772536f00>{number = 3, name = (null)}
2015-07-28 03:40:34.278 test[12540:3319146] group-02 - <NSThread: 0x7f977240ba60>{number = 1, name = main}
2015-07-28 03:40:34.278 test[12540:3319273] group-01 - <NSThread: 0x7f977272e8d0>{number = 2, name = (null)}
2015-07-28 03:40:34.278 test[12540:3319146] group-02 - <NSThread: 0x7f977240ba60>{number = 1, name = main}
2015-07-28 03:40:34.278 test[12540:3319273] group-01 - <NSThread: 0x7f977272e8d0>{number = 2, name = (null)}
2015-07-28 03:40:34.279 test[12540:3319146] group-02 - <NSThread: 0x7f977240ba60>{number = 1, name = main}
2015-07-28 03:40:34.279 test[12540:3319146] group-02 - <NSThread: 0x7f977240ba60>{number = 1, name = main}
2015-07-28 03:40:34.279 test[12540:3319146] 完成 - <NSThread: 0x7f977240ba60>{number = 1, name = main}

六、GCD 计时器应用

NSTimer 的定时器是在 RunLoop 中实现的,由于RunLoop在处理各种任务,所以会造成计时器不够准确,有时候会相对慢一些,有没有什么方法会让计时变得准确?有,使用 GCD 的计时器方法会让计时器变得相对准确,而且GCD不受RunLoop的 Mode 影响。

我们需要做的是,选择其队列类型,这里我选择的是全局队列。

dispatch Queue :决定了将来回调的方法在哪里执行。

dispatch_source_set_timer 方法:
// dispatch_source_t timer 第一个参数: 是一个OC对象

// DISPATCH_TIME_NOW 第二个参数:定时器开始时间,也可以使用如下的方法,在Now 的时间基础上再延时多长时间执行以下任务。

 dispatch_time(<#dispatch_time_t when#>, <#int64_t delta#>)
 
 dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, 0 * NSEC_PER_SEC);
//    dispatch_time_t start = dispatch_walltime(NULL, 0);

// intervalInSeconds 第三个参数:定时器开始后的间隔时间(纳秒 NSEC_PER_SEC)

// leewayInSeconds 第四个参数:间隔精准度,0代标最精准,传入一个大于0的数,代表多少秒的范围是可以接收的,主要为了提高程序性能,积攒一定的时间,Runloop执行完任务会睡觉,这个方法让他多睡一会,积攒时间,任务也就相应多了一点,而后一起执行

 // 全局队列
dispatch_queue_t  queue = dispatch_get_global_queue(0, 0);
// 创建一个 timer 类型定时器 ( DISPATCH_SOURCE_TYPE_TIMER)
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
//设置定时器的各种属性(何时开始,间隔多久执行)
// GCD 的时间参数一般为纳秒 (1 秒 = 10 的 9 次方 纳秒)
// 指定定时器开始的时间和间隔的时间
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC, 0);
// 任务回调
dispatch_source_set_event_handler(timer, ^{
    NSLog(@"-----定时器-------");
});
// 开始定时器任务(定时器默认开始是暂停的,需要复位开启)
dispatch_resume(timer);

开始定时器

dispatch_resume(self.timer);

暂停定时器

dispatch_suspend(self.timer);

取消定时器

dispatch_cancel(self.timer);
self.timer = nil;

GCD实现验证码倒计时按钮案例:

// 开启倒计时效果
(IBAction)openCountdown:(id)sender {
    __block NSInteger time = 59; //倒计时时间
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
 dispatch_source_set_timer(timer,DISPATCH_TIME_NOW,1.0*NSEC_PER_SEC, 0); //每秒执行
    dispatch_source_set_event_handler(timer, ^{
        if(time <= 0){ //倒计时结束,关闭
            dispatch_source_cancel(timer);
            dispatch_async(dispatch_get_main_queue(), ^{
                //设置按钮的样式
                [self.openSeconds setTitle:@"重新发送" forState:UIControlStateNormal];
                self.timeLabel.text = @"开始";
                self.openSeconds.userInteractionEnabled = YES;
            });
        }else{
            int seconds = time % 60;
            dispatch_async(dispatch_get_main_queue(), ^{
                //设置label读秒效果
                self.timeLabel.text = [NSString stringWithFormat:@"重新发送(%.2d)",seconds];
                [self.openSeconds setTitle:@"已发送" forState:UIControlStateNormal];
              // 在这个状态下 用户交互关闭,防止再次点击 button 再次计时
                self.openSeconds.userInteractionEnabled = NO;
            });
            time--;
        }
    });
    dispatch_resume(timer);
}

七、栅栏-dispatch_barrier

需求点:虽然我们有时要执行几个不同的异步任务,但是我们还是要将其分成两组:当第一组异步任务都执行完成后才执行第二组的异步任务。这里的组可以包含一个任务,也可以包含多个任务。
为了实现这个需求,我们需要使用dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
在两组任务之间形成“栅栏”,使其“下方”的异步任务在其“上方”的异步任务都完成之前是无法执行的。

    dispatch_queue_t queue = dispatch_queue_create("12312312", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"----任务 1-----"); });
    dispatch_async(queue, ^{
        NSLog(@"----任务 2-----"); });
    
    dispatch_barrier_async(queue, ^{
        NSLog(@"----barrier-----");
    });
    
    dispatch_async(queue, ^{
        NSLog(@"----任务 3-----");
    });
    dispatch_async(queue, ^{
        NSLog(@"----任务 4-----"); });

为了更好的理解同步和异步,和各种队列的使用,下面看两个示例:

面试题: 以下代码在主线程调用,结果是什么?

NSLog("之前 - %@", NSThread.currentThread())
dispatch_sync(dispatch_get_main_queue(), { () -> Void in 
        NSLog("sync - %@", NSThread.currentThread())
})
NSLog("之后 - %@", NSThread.currentThread())

答案

只会打印第一句:之前 - <NSThread: 0x7fb3a9e16470>{number = 1, name = main} ,然后主线程就卡死了,你可以在界面上放一个按钮,你就会发现点不了了。

解释

同步任务会阻塞当前线程,然后把 Block 中的任务放到指定的队列中执行,只有等到 Block 中的任务完成后才会让当前线程继续往下运行。

那么这里的步骤就是:打印完第一句后,dispatch_sync 立即阻塞当前的主线程,然后把 Block 中的任务放到 main_queue 中,可是 main_queue 中的任务会被取出来放到主线程中执行,但主线程这个时候已经被阻塞了,所以 Block 中的任务就不能完成,它不完成,dispatch_sync 就会一直阻塞主线程,这就是死锁现象。导致主线程一直卡死。

面试题: 以下代码会产生什么结果?

let queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL)

NSLog("之前 - %@", NSThread.currentThread())

dispatch_async(queue, { () -> Void in
    NSLog("sync之前 - %@", NSThread.currentThread())
    dispatch_sync(queue, { () -> Void in
         NSLog("sync - %@", NSThread.currentThread())
    })
    NSLog("sync之后 - %@", NSThread.currentThread())
})

NSLog("之后 - %@", NSThread.currentThread())

答案:

2015-07-30 02:06:51.058 test[33329:8793087] 之前 - <NSThread: 0x7fe32050dbb0>{number = 1, name = main}
2015-07-30 02:06:51.059 test[33329:8793356] sync之前 - <NSThread: 0x7fe32062e9f0>{number = 2, name = (null)}
2015-07-30 02:06:51.059 test[33329:8793087] 之后 - <NSThread: 0x7fe32050dbb0>{number = 1, name = main}

很明显 sync - %@sync之后 - %@ 没有被打印出来!这是为什么呢?我们再来分析一下:

分析:

我们按执行顺序一步步来哦:

  1. 使用 DISPATCH_QUEUE_SERIAL 这个参数,创建了一个 串行队列
  2. 打印出 之前 - %@ 这句。
  3. dispatch_async 异步执行,所以当前线程不会被阻塞,于是有了两条线程,一条当前线程继续往下打印出 之后 - %@这句, 另一台执行 Block 中的内容打印 sync之前 - %@ 这句。因为这两条是并行的,所以打印的先后顺序无所谓。
  4. 注意,高潮来了。现在的情况和上一个例子一样了。dispatch_sync同步执行,于是它所在的线程会被阻塞,一直等到 sync 里的任务执行完才会继续往下。于是 sync 就高兴的把自己 Block 中的任务放到 queue 中,可谁想 queue 是一个串行队列,一次执行一个任务,所以 sync 的 Block 必须等到前一个任务执行完毕,可万万没想到的是 queue 正在执行的任务就是被 sync 阻塞了的那个。于是又发生了死锁。所以 sync 所在的线程被卡死了。剩下的两句代码自然不会打印。

GCD总结

  • 同步异步函数的作用:将任务添加到队列中
  • 队列作用:决定任务执行的顺序,先添加的先执行,最后添加的任务最后执行
  • 同步函数:堵塞当前线程;需要等待任务结束才能返回;在当前线程中进行;无开辟线程的权限
  • 异步函数:不会堵塞当前线程;不需要等待任务;有开辟线程的权限
  • 队列和任务关系;任务放在队列里
  • 任务跟线程关系:任务需要线程来执行;
  • 线程跟队列关系:一个队列里可能有好多线程,在一个线程内可能也有多个队列;在某个线程里创建队列,那么这个队列是属于这个线程的;
  • 主队列和主线程的关系:主队列是主线中的一个串行队列。所有的和UI的操作(刷新或者点击按钮)都必须在主线程中的主队列中去执行,否则无法更新UI,每一个应用程序只有唯一的一个主队列用来update UI。如果在主线里创建了一个自定义的队列,那么这个队列也就属于主线程的队列。
  • 注意重点:如果在主线程里创建了一个自定义的队列,且如果主线程在主队列中被堵塞了,那么主线程会跑到这个自定义队列里看看有没有任务,如果有就执行。
  • 如果在主线程中创建自定义队列(串行或者并行均可),在这个队列中执行同步任务,同样可以更新UI操作,主队列中可以更新UI,自定义队列也可以更新UI,但自定义队列的更新UI的前提是在主线程中执行同步任务

面试题: 同步主队列死锁原因

在主线程里,开启同步任务,并打算使用主队列,当开始执行同步函数的时候,这时候发生了什么:

  1. 主队列会把同步函数任务放到主队列的最前面也就是最先执行的地方
  2. 同步函数堵塞当前的线程也就是主线程,并把block中的任务添加到主队列,这时候block中的任务跟同步任务都在同一个线程、同一个队列,block中的任务排在同步任务的后面,等待前面任务(同步任务)的执行。
  3. 同步函数的特点是必须等待block中的任务执行完才能返回,但是同步任务和block中的任务都在主队列里,根据先进先执行(FIFO)的原则,会发生这样的无限循环现象:同步任务等待block任务执行完,但是同步任务又排在了block任务的前面,block任务不能执行,那就等于同步任务也不能执行完成,所以产生死锁现象。

面试题: 同步串行队列不会发生死锁原因

在主线程里,开启同步任务,并自定义串行队列,当开始执行同步函数的时候,这时候发生了什么:

  1. 主队列会把同步函数任务放到主队列的最前面也就是最先执行的地方
  2. 同步函数堵塞当前的线程也就是主线程,并把block中的任务添加到自定义串行队列,这时候,在主线程也就有了两个队列,block中的任务跟同步任务都在同一个线程,但是不在同一个队列。
  3. 主线程在主队列被同步函数堵塞,会跑到它的其他队列里,看看有没有要执行的任务。所以它会去自定义队列里去执行任务。
  4. 同步函数的特点是必须等待block中的任务执行完才能返回,自定义队列里的任务已被执行完,同步函数也就执行完返回主线程。

重点:虽然主队列中的主线程被堵塞了,但是主线程可以去执行其他属于主线程的队列的任务。

(原文链接)

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

推荐阅读更多精彩内容