iOS开发多线程下全局变量赋值崩溃原理详解

目录
  • 问题 Demo
  • 崩溃原因
  • 崩溃路径
  • 验证方式
  • 其它测试

问题 Demo

在多线程下同时给全局变量赋值时会发生崩溃:

static NSObject *_instance;
- (void)foo {
    _instance = [[NSObject alloc] init];
}

崩溃原因

如下为源码的汇编代码:

Demo-iOS`-[ViewController foo]:
    0x104e4e088 <+0>:  stp    x29, x30, [sp, #-0x10]!
    0x104e4e08c <+4>:  mov    x29, sp
    # newValue = [[NSObject alloc] init]
    0x104e4e094 <+12>: ldr    x0, #0x7454               ; (void *)0x00000001db209e08: NSObject
    0x104e4e098 <+16>: bl     0x104e4e438               ; symbol stub for: objc_alloc_init
    # oldValue = _instance
    0x104e4e09c <+20>: adrp   x9, 7
    0x104e4e0a0 <+24>: ldr    x8, [x9, #0x788]
    # _instance = newValue
    0x104e4e0a4 <+28>: str    x0, [x9, #0x788]
    # objc_release(oldValue)
    0x104e4e0a8 <+32>: mov    x0, x8
    0x104e4e0ac <+36>: ldp    x29, x30, [sp], #0x10
    0x104e4e0b0 <+40>: b      0x104e4e480               ; symbol stub for: objc_release

对汇编代码进行反汇编,可以看出 ARC 下编译器添加了读取旧值 oldValue = _instance 和释放旧值 objc_release(oldValue) 的操作:

- (void)foo {
    NSObject *newValue = [[NSObject alloc] init];
    NSObject *oldValue = _instance; //读取旧值
    _instance = newValue;
    objc_release(oldValue); //释放旧值
}

给全局变量赋值时会读取旧值、释放旧值,旧值是从全局变量读取的,多个线程可以同时读到同一个值,如果一个 线程 在访问旧值时,旧值被其它线程销毁,就会发生崩溃。

即使在代码中添加了判空逻辑,也会有可能多个线程同时进入 if (!_instance) 里,发生错误:

static NSObject *_instance;
- (void)foo {
    if (!_instance) {
        _instance = [[NSObject alloc] init];
    }
}

即使不崩溃,多个线程也会产生不同的实例,是不符合预期的

崩溃路径

可以推断出一种复现崩溃的办法:

  • A B C 线程同时进入 - (NSObject *)foo 方法
  • A 线程先创建 NSObject 实例,赋值给 _instance (_instance = newValue),_instance 引用计数为 1
  • B、C 线程再开始执行,执行到 oldValue = _instance 时,会从 _instance 全局变量中读到 A 线程创建的对象,赋值给各自的 oldValue,oldValue 引用计数为 1
  • B 线程在 objc_release(oldValue) 后会释放 oldValue,oldValue 引用计数为 0,oldValue 被销毁
  • C 线程在 objc_release(oldValue) 时访问 oldValue,发生崩溃

验证方式

lldb 的 thread continue 指令可以控制仅一个线程执行,其它线程保持挂起。

利用该指令,可以复现崩溃路径,按下面步骤可以验证:

  • 准备三个线程执行 [self foo],并在 -foo 方法里面打上断点:

可以多次测试让 3 个 线程 同时进入断点,进入断点后可以看到 Thread 2、3、4 是创建的 3 个线程:

不加 asm("nop\n") 的话执行完 objc_release(oldValue) 后,foo 函数会直接结束,不太方便在 objc_release(oldValue) 之后打断点进行调试,添加之后 objc_release 之后会有位置打断点(第 4 5 步用到)

  • 在 Thread 2 中给汇编代码第 10 行打断点,并执行 thread contine,使 Thread 2 执行完 _instance = newValue :

可以看到 Thread 2 创建的实例为 0x0000000280df8020

  • 使 Thread 3、4 线程 执行完 oldValue = _instance

步骤1:删除断点(每次切换线程都要删掉断点,不然 Xcode 可能会有 bug ...),切换到 Thread 3 ,给第 9 行打断点,并执行 thread continue:

在 Xcode Debug Navitor 中选择线程堆栈可以切换线程

或者使用 lldb,thread select 3 切换线程

步骤2:删除断点,切换到 Thread 4,给第 9 行打断点,并执行 thread continue:

可以发现 Thread 3、4 读到的旧值都是 Thread 2 创建的 0x0000000280df8020

  • 使Thread 3 执行完 objc_release(oldValue)

步骤:删除断点,切换到 Thread 3,给第 12 行打断点,并执行 thread continue:

此时 oldValue 引用计数为 0,被销毁

  • 使 Thread 4 执行 objc_release(oldValue), 访问 oldValue

步骤:删除断点,切换到 Thread 4,给第 12 行打断点,并执行 thread continue:

在 Thread 3 执行 objc_release(oldValue) 后 oldValue 就已经被销毁了,

Thread 4 再次访问时会发生崩溃

其它测试

  • 对成员变量赋值时同样有这个问题
 @property (nonatomic, strong) NSObject *obj;
- (NSObject *)getInstance {
    _obj = [[NSObject alloc] init];
    return _obj;
}

  • 局部变量不会有这个问题

局部变量不涉及"将旧值释放"这个操作。

以上就是iOS开发多线程下全局变量赋值崩溃原理详解的详细内容,更多关于多线程全局变量赋值崩溃的资料请关注我们其它相关文章!

(0)

相关推荐

  • iOS中多线程的经典崩溃总结大全

    前言 iOS崩溃是让iOS开发人员比较头痛的事情,app崩溃了,说明代码写的有问题,这时如何快速定位到崩溃的地方很重要.调试阶段是比较容易找到出问题的地方的,但是已经上线的app并分析崩溃报告就比较麻烦了. 本文将给大家总结介绍关于iOS中多线程的一些经典崩溃,下面话不多说了,来一起看看详细的介绍吧. 0x0 Block 回调的崩溃 在MRC环境下,使用Block 来设置下载成功的图片.当self释放后,weakSelf变成野指针,接着就悲剧了 __block ViewController *w

  • iOS多线程实现多图下载功能

    本文实例为大家分享了iOS多线程实现多图下载功能的具体代码,供大家参考,具体内容如下 一.模型文件代码如下 // XMGAPP.h #import <Foundation/Foundation.h> @interface XMGAPP : NSObject /** APP的名称 */ @property (nonatomic, strong) NSString *name; /** APP的图片的url地址 */ @property (nonatomic, strong) NSString *

  • iOS开发中多线程的安全隐患总结

    资源共享 1块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源 比如多个线程访问同一个对象.同一个变量.同一个文件 当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题 一.解决方案 解决方案:使用线程同步技术(同步,就是协同步调,按预定的先后次序进行) 常见的线程同步技术是:加锁 1.OSSpinLock OSSpinLock叫做"自旋锁",等待锁的线程会处于忙等(busy-wait)状态,一直占用着CPU资源 目前已经不再安全,可能会出现优先级反转问题 如果等待

  • iOS中的多线程如何按设定顺序去执行任务详解

    多线程概述 对于ios系统中的某个App来讲,是单进程多线程方式来工作.一般来说,使用多线程的好处是可以把程序分成相对独立的几个模块,可以有效的防止某个模块堵塞的时候导致整个程序卡死:还有就是提高运行效率,现在CPU都是多核,多个核可以同时跑,可以同时执行多条线程. 经常有这样的需求: 1,有m个网络请求. 2,先并发执行其中n几个. 3,待这n个请求完成之后再执行第n+1个请求. 4然后等 第n+1个请求完成后再并发执行剩下的m-(n+1)个请求. 如果我们用GCD,可以使用dispatcg_

  • iOS 多线程总结之GCD的使用详解

    进程与线程 进程就是一个应用程序在处理机上的一次执行过程,它是一个动态的概念,而线程是进程中的一部分,进程包含多个线程在运行. 线程是指进程内的一个执行单元,也是进程内的可调度实体. 与进程的区别: (1)地址空间:线程是进程内的一个执行单元;进程至少有一个线程;它们共享进程的地址空间;而进程有自己独立的地址空间; (2)资源拥有:进程是资源分配和拥有的单位,同一个进程内的线程共享进程的资源 (3)线程是处理器调度的基本单位,但进程不是. (4)二者均可并发执行. GCD 1.什么是GCD? 全

  • 探究iOS多线程究竟不安全在哪里?

    前言 共享状态,多线程共同访问某个对象的property,在iOS编程里是很普遍的使用场景,我们就从Property的多线程安全说起. Property 当我们讨论property多线程安全的时候,很多人都知道给property加上atomic attribute之后,可以一定程度的保障多线程安全,类似: @property (atomic, strong) NSString* userName; 事情并没有看上去这么简单,要分析property在多线程场景下的表现,需要先对property的类

  • iOS开发多线程下全局变量赋值崩溃原理详解

    目录 问题 Demo 崩溃原因 崩溃路径 验证方式 其它测试 问题 Demo 在多线程下同时给全局变量赋值时会发生崩溃: static NSObject *_instance; - (void)foo { _instance = [[NSObject alloc] init]; } 崩溃原因 如下为源码的汇编代码: Demo-iOS`-[ViewController foo]: 0x104e4e088 <+0>: stp x29, x30, [sp, #-0x10]! 0x104e4e08c

  • IOS开发之字典转字符串的实例详解

    IOS开发之字典转字符串的实例详解 在实际的开发需求时,有时候我们需要对某些对象进行打包,最后拼接到参数中 例如,我们把所有的参数字典打包为一个 字符串拼接到参数中 思路:利用系统系统JSON序列化类即可,NSData作为中间桥梁 //1.字典转换为字符串(JSON格式),利用 NSData作为桥梁; NSDictionary *dic = @{@"name":@"Lisi",@"sex":@"m",@"tel&qu

  • IOS开发中NSURL的基本操作及用法详解

    NSURL其实就是我们在浏览器上看到的网站地址,这不就是一个字符串么,为什么还要在写一个NSURL呢,主要是因为网站地址的字符串都比较复杂,包括很多请求参数,这样在请求过程中需要解析出来每个部门,所以封装一个NSURL,操作很方便. 1.URL URL是对可以从互联网上得到的资源的位置和访问方法的一种简洁的表示,是互联网上标准资源的地址.互联网上的每个文件都有一个唯一的URL,它包含的信息指出文件的位置以及浏览器应该怎么处理它. URL可能包含远程服务器上的资源的位置,本地磁盘上的文件的路径,甚

  • iOS开发中如何优雅的调试数据库详解

    背景 写代码难免出现bug. 储备些调试技能绝对能够提高你的工作效率,让bug无所遁形.相信大家应该都有所体会,我们在开发的时候,数据库的操作一直是一个很棘手的问题,后来发现Android下面有一个第三方的库还挺好用的,就模仿它搞了个iOS的,可以方便的通过浏览器查看.添加.删除.修改数据库.下面话不多说了,来一看看详细的介绍吧. 历史状况 我们来回想一下调试的过程: 如果在模拟器中调试: 找到模拟器应用中数据库的文件位置 拷回到一个比较方便打开的地方 安装一个数据库操作软件 打开数据库文件 s

  • iOS复数cell下优雅的代码结构详解

    前言 最近换了新工作,第一个需求是写几个列表. 简单的UITableView+Cell,但毕竟是入职后的第一个需求感觉要被review,所以还是想尽量弄得优雅一点. 下面话不多说了,来一起看看详细的介绍吧 需求 一个页面,可能出现多种cell. 这个需求应该是很常见的,需要解决的问题是如何让多个cell能够共同响应同一个方法,这样外部不需要知道具体的cell种类,只要调用同一个方法进行配置即可. 问了问朋友们大家基本上是两派. 协议 基类 我个人以前也是用协议对多个cell进行约束的,通过让ce

  • iOS开发技巧之WeakSelf宏的进化详解

    前言 本文主要给大家介绍了关于iOS之WeakSelf宏的进化的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. WeakSelf宏的进化 我们都知道在防止如block的循环引用时,会使用__weak关键字做如下定义: __weak typeof(self) weakSelf = self; 后来,为了方便,不用每次都要写这样一句固定代码,我们定义了宏: #define WeakSelf __weak typeof(self) weakSelf = self; 之后,我

  • Java多线程实现四种方式原理详解

    1.继承Thread类,重写run方法 2.实现Runnable接口,重写run方法,实现Runnable接口的实现类的实例对象作为Thread构造函数的target 3.通过Callable和FutureTask创建线程 4.通过线程池创建线程 前面两种可以归结为一类:无返回值,原因很简单,通过重写run方法,run方式的返回值是void,所以没有办法返回结果 后面两种可以归结成一类:有返回值,通过Callable接口,就要实现call方法,这个方法的返回值是Object,所以返回的结果可以放

  • Java多线程 线程状态原理详解

    这篇文章主要介绍了Java多线程 线程状态原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 java.lang.Thread.State枚举定义了6种线程状态. NEW: 尚未启动(start)的线程的线程状态 RUNNABLE: 运行状态,但线程可能正在JVM中执行,也可能在等待CPU调度 BLOCKED: 线程阻塞,等待监视器锁以进入同步代码块/方法 WAITING: 等待状态.使用以下不带超时的方式时会进入:Object.wait.

  • JSP动态网页开发原理详解

    一.什么是JSP?     JSP全称是Java Server Pages,它和servle技术一样,都是SUN公司定义的一种用于开发动态web资源的技术. JSP这门技术的最大的特点在于,写jsp就像在写html,但它相比html而言,html只能为用户提供静态数据,而Jsp技术允许在页面中嵌套java代码,为用户提供动态数据. 二.JSP原理 2.1.Web服务器是如何调用并执行一个jsp页面的? 浏览器向服务器发请求,不管访问的是什么资源,其实都是在访问Servlet,所以当访问一个jsp

  • Java并发编程深入理解之Synchronized的使用及底层原理详解 下

    目录 一.synchronized锁优化 1.自旋锁与自适应自旋 2.锁消除 逃逸分析: 3.锁粗化 二.对象头内存布局 三.synchronized锁的膨胀升级过程 1.偏向锁 2.轻量级锁 3.重量级锁 4.各种锁的优缺点 接着上文<Java并发编程深入理解之Synchronized的使用及底层原理详解 上>继续介绍synchronized 一.synchronized锁优化 高效并发是从JDK 5升级到JDK 6后一项重要的改进项,HotSpot虚拟机开发团队在这个版本上花费了大量的资源

随机推荐