iOS - 多线程-GCD

文章目录

  • [iOS - 多线程-GCD](#iOS - 多线程-GCD)
    • [1. 常见多线程方案](#1. 常见多线程方案)
    • [2. GCD](#2. GCD)
      • [2.1 GCD的常见函数](#2.1 GCD的常见函数)
      • [2.2 GCD的队列](#2.2 GCD的队列)
        • [2.2.1 GCD的队列可以分为2大类型](#2.2.1 GCD的队列可以分为2大类型)
      • [2.3 容易混淆的术语](#2.3 容易混淆的术语)
        • [2.4.1 有4个术语比较容易混淆:`同步、异步`、`并发、串行`](#2.4.1 有4个术语比较容易混淆:同步、异步并发、串行)
      • [2.4 各种队列的执行效果](#2.4 各种队列的执行效果)
    • [3. 死锁](#3. 死锁)
      • [3.1 死锁示例](#3.1 死锁示例)
      • [3.2 死锁分析](#3.2 死锁分析)
      • [3.3 其他示例](#3.3 其他示例)
      • [3.3.1 interview02](#3.3.1 interview02)
      • [3.3.2 interview03](#3.3.2 interview03)
      • [3.3.3 interview04](#3.3.3 interview04)
    • [4. 案例](#4. 案例)
      • [4.1 案例1](#4.1 案例1)
      • [4.2 分析](#4.2 分析)
    • [5. 拓展](#5. 拓展)

iOS - 多线程-GCD

1. 常见多线程方案

NSThreadGCDNSOperation底层都依赖于pthread

2. GCD

2.1 GCD的常见函数

GCD中有2个用来执行任务的函数
  • 同步的方式执行任务

    dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);

    1. queue:队列
    2. block:任务
  • 异步的方式执行任务

    dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

  • GCD源码:https://github.com/apple/swift-corelibs-libdispatch

2.2 GCD的队列

2.2.1 GCD的队列可以分为2大类型
  • 并发队列(Concurrent Dispatch Queue)

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

    1. 让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)

2.3 容易混淆的术语

2.4.1 有4个术语比较容易混淆:同步、异步并发、串行
  • 同步异步主要影响:能不能开启新的线程

    • 同步:在当前线程中执行任务,不具备开启新线程的能力
    • 异步:在新的线程中执行任务,具备开启新线程的能力
  • 并发串行主要影响:任务的执行方式

    • 并发多个任务并发(同时)执行
    • 串行一个任务执行完毕后,再执行下一个任务

2.4 各种队列的执行效果

  • 使用sync函数往当前串行队列中添加任务,会卡住当前的串行队列(产生死锁)

3. 死锁

3.1 死锁示例

复制代码
// 问题:以下代码是在主线程执行的,会不会产生死锁?
NSLog(@"执行任务1");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
   NSLog(@"执行任务2");
});
NSLog(@"执行任务3");

3.2 死锁分析

如上代码

  • 正常情况应该是顺序执行任务1任务2任务3
  • 任务2使用dispatch_sync同步执行方式,放入主线程队列,因此任务2需要排队等待前面的任务执行完成后才执行
  • 但是当前方法体viewDidLoad可以认为就是一个任务在执行,但是执行到任务2dispatch_sync处,会等待dispatch_sync执行完成再继续往下执行
  • 此时,相当于任务2等待当前执行任务执行完成,当前执行任务也在等待任务2执行完成,相互等待因此造成线程死锁

3.3 其他示例

3.3.1 interview02

复制代码
- (void)interview02 {
    // 问题:interview01中的sync,改成 async。会不会产生死锁?不会!
    /* 打印日志:
        执行任务1
        执行任务3
        执行任务2
     */
    NSLog(@"执行任务1");
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_async(queue, ^{
        NSLog(@"执行任务2");
    });
    NSLog(@"执行任务3");
}

3.3.2 interview03

复制代码
- (void)interview03 {
    // 会不会产生死锁?会!
    /*
     分析:
     执行任务2 后,同步等待任务2 执行,但是因为queue是串行的,所以会相互等待
     */
    NSLog(@"执行任务1");
    dispatch_queue_t queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        NSLog(@"执行任务2");
        dispatch_sync(queue, ^{
            NSLog(@"执行任务3");
        });
        NSLog(@"执行任务4");
    });
    NSLog(@"执行任务5");
}

3.3.3 interview04

复制代码
- (void)interview04 {
    // interview03改为并发队列(DISPATCH_QUEUE_CONCURRENT),会不会死锁?不会!
    NSLog(@"执行任务1");
    dispatch_queue_t queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"执行任务2");
        dispatch_sync(queue, ^{
            NSLog(@"执行任务3");
        });
        NSLog(@"执行任务4");
    });
    NSLog(@"执行任务5");
}

4. 案例

4.1 案例1

如下代码打印什么:

复制代码
- (void)test {
    NSLog(@"2");
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
        NSLog(@"1");
        [self performSelector:@selector(test) withObject:nil afterDelay:0];
        NSLog(@"3");
    });
}

打印结果:

观察到,2不会打印

去掉dispatch_async又会怎么样

复制代码
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"1");
    [self performSelector:@selector(test) withObject:nil afterDelay:0];
    NSLog(@"3");
}

这时候都有打印,只不过打印顺序1>3>2

接着,回到dispatch_async里执行,但是把afterDelay去掉

复制代码
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
        NSLog(@"1");
        [self performSelector:@selector(test) withObject:nil];
        NSLog(@"3");
    });
}

打印结果是1>2>3,这次打印顺序是正常的

4.2 分析

上面的例子中,主要是考察runloop多线程的相关知识

  • 首先,在dispatch_async中使用- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;方法来执行,使用afterDelay:0看似是在没有延迟的情况下执行,实际上因为该方法是基于 runloop的,相当于往runloop添加一个定时器,但是因为此时我们是在子线程中执行的,子线程中的runloop默认不会开启,所以test方法没有执行。我们尝试开启runloop

    dispatch_async(queue, ^{
    NSLog(@"1");
    [self performSelector:@selector(test) withObject:nil afterDelay:0];
    NSLog(@"3");

    复制代码
      [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc]init] forMode:NSDefaultRunLoopMode];
      [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];

    });

可以看到,2打印了

  • 接着,我们去掉了dispatch_async,发现打印结果是1>3>2,为什么3会比2先打印的?

    使用afterDelay:0看似是在没有延迟的情况下执行,实际上因为该方法是基于 runloop的定时器,虽然没有延迟设置为 0,但是runloop的定时器是在被唤醒的时候处理定时器的,但是在进入休眠之前会处理完点击事件,因此看到的打印结果是13先打印,然后打印2

  • 最后,回到dispatch_async中执行,只不过使用的是- (id)performSelector:(SEL)aSelector withObject:(id)object;方法来执行,查看源码

    该方法实际是直接使用objc_msgSend方法执行,相当于我们直接[self test]这样调用方法,所以这时候打印顺序是正常的1>2>3

5. 拓展

GNUstep

GNUstep是GNU计划的项目之一,它将Cocoa的OC库重新开源实现了一遍

源码地址:https://gnustep.github.io/resources/downloads.html

虽然GNUstep不是苹果官方源码,但还是具有一定的参考价值

@oubijiexi

相关推荐
若水无华1 天前
fiddler 配置ios手机代理调试
ios·智能手机·fiddler
Aress"1 天前
【ios越狱包安装失败?uniapp导出ipa文件如何安装到苹果手机】苹果IOS直接安装IPA文件
ios·uni-app·ipa安装
Jouzzy2 天前
【iOS安全】Dopamine越狱 iPhone X iOS 16.6 (20G75) | 解决Jailbreak failed with error
安全·ios·iphone
瓜子三百克2 天前
采用sherpa-onnx 实现 ios语音唤起的调研
macos·ios·cocoa
左钦杨2 天前
IOS CSS3 right transformX 动画卡顿 回弹
前端·ios·css3
努力成为包租婆2 天前
SDK does not contain ‘libarclite‘ at the path
ios
安和昂3 天前
【iOS】Tagged Pointer
macos·ios·cocoa
I烟雨云渊T3 天前
iOS 阅后即焚功能的实现
macos·ios·cocoa
struggle20253 天前
适用于 iOS 的 开源Ultralytics YOLO:应用程序和 Swift 软件包,用于在您自己的 iOS 应用程序中运行 YOLO
yolo·ios·开源·app·swift
Unlimitedz3 天前
iOS视频编码详细步骤(视频编码器,基于 VideoToolbox,支持硬件编码 H264/H265)
ios·音视频