iOS - 多线程-GCD

devtools/2024/10/18 21:22:51/

文章目录

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

iOS__GCD_1">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


http://www.ppmy.cn/devtools/15567.html

相关文章

Stream流对list<map>的操作

Map<String,Object> map new HashMap<>();map.put("name","张三");map.put("age","30");map.put("sex","男");map.put("addr","深圳");List<Map<String,Object>> l…

WPF —— lCommand命令实例

首先在标签页面设置一个Button按钮 <Button Width"100" Height"40" Content"测试" ></Button> 1 创建一个类 继承于ICommand这个接口&#xff0c; 这个接口一般包含三部分&#xff1a; 俩个方法&#xff1a;一个判断指令是不是…

基于Splinter演示如何使用Chrome WebDriver

关注开源优测不迷路 大数据测试过程、策略及挑战 测试框架原理&#xff0c;构建成功的基石 在自动化测试工作之前&#xff0c;你应该知道的10条建议 在自动化测试中&#xff0c;重要的不是工具 Chrome WebDriver由selenium提供的chrome浏览器驱动&#xff0c;在使用它前&#x…

力扣216---组合总和III(Java、递归回溯)

目录 题目描述&#xff1a; 思路描述&#xff1a; 代码&#xff1a; 题目描述&#xff1a; 找出所有相加之和为 n 的 k 个数的组合&#xff0c;且满足下列条件&#xff1a; 只使用数字1到9每个数字 最多使用一次 返回 所有可能的有效组合的列表 。该列表不能包含相同的组…

tar 和 zip 打包压缩命令

1. tar 文件的归档 tar [选项] 归档压缩后生成的文件 打包文件常用参数&#xff1a;-c # 创建文件-x # 提取解压还原文件-v # 显示详细执行过程-f # 指定备份文件-t # 列出压缩包中包括哪些文件&#xff0c;不解包&#xff0c;查看包中的内容-C # 指定解压位置 #对/o…

Unity中Socket,Tcp,Udp网络连接协议总结

Socket连接 Socket连接介绍 这里Socket先使用Tcp协议同步连接&#xff0c;Tcp协议作为稳定协议,在消息发送前必须完成客户端连接,且客户端连接在Tcp协议中只能是一对一的,即如果有ABC三个连接,那个A连接与B连接如果相互连接,则A与C之间则无法互相通信,只能由A接受到消息时创建…

学习笔记-数据结构-线性表(2024-04-17)

设计一个算法实现在单链表中删除值相同的多余节点的算法。 设计思想&#xff1a;双指针 变量说明&#xff1a; head - 参数变量&#xff0c;代表链表的头节点。在调用DelSameNum函数时&#xff0c;需要传递链表的头节点的地址给这个参数&#xff0c;从而允许函数对链表进行操作…

如何快速申请SSL证书实现HTTPS访问?

申请SSL证书最简单的方法通常涉及以下几个步骤&#xff0c;尽量简化了操作流程和所需专业知识&#xff1a; 步骤一&#xff1a;选择适合的SSL证书类型 根据您的网站需求&#xff0c;选择最基础的域名验证型&#xff08;DV SSL&#xff09;证书&#xff0c;它通常只需验证域名所…