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

相关推荐
9765033352 小时前
iOS 上架 4.3a 被拒【uniapp专讲】
flutter·ios·objective-c·uniapp·swift
黑化旺仔15 小时前
【OC】KVO
macos·ios·objective-c·cocoa
HouWan17 小时前
SwiftUI 小技巧:如何读取 SwiftUI Font 的详细信息
ios·swiftui·apple
李游Leo17 小时前
HarmonyOS 7 实战开发 05:把页面做到可上线状态
ios·harmonyos
终端安全笔记1 天前
描述文件、企业证书、MDM 不是三个名字,是三层
android·ios·智能手机
传奇开心果编程1 天前
【SwiftUI入门练中学】第1课 从零开始
学习·ui·ios·swiftui·swift
丿Wayne1 天前
iOS Token 刷新实战(Actor 避免并发重复刷新)
ios·swift·token·actor·swift并发
终端安全笔记2 天前
iOS 27 给了租赁一个新工具,但它只认受监督的设备
android·网络·安全·ios
大熊猫侯佩2 天前
独立 App 首发完成 iPhone Duo 展开态适配
ios·swiftui·swift
光影少年2 天前
setImmediate 和 setTimeout(0) 的区别
android·前端·react.js·ios·前端框架