多线程 循环 锁_大多数人还不清楚的iOS多线程

你不知道的的 iOS 多线程

程序员用有限的生命去追求无限的知识。

有言在先

首先我不是故意要做标题党的,也不是我要炒冷饭,我只是想换个姿势看多线程,本文大部分内容在分析如何造死锁,奈何功力尚浅,然而再浅,也需要走出第一步。打开你的 Xcode 来验证这些死锁吧。

多线程小知识

以下是实现多线程的三种方式:

  • NSThread
  • GCD
  • NSOperationQueue

关于具体使用的方法不再具体介绍,让我们来看看他们不为人知的一面

1. 锁的背后

NSLock是基于 POSIX threads 实现的,而 POSIX threads 中使用互斥量同步线程。

互斥量(或称为互斥锁)是 pthread 库为解决这个问题提供的一个基本的机制。互斥量是一个锁,它保证如下三件事情:

  • 原子性 - 锁住一个互斥量是一个原子操作,表明操作系统保证如果在你已经锁了一个互斥量,那么在同一时刻就不会有其他线程能够锁住这个互斥量;
  • 奇异性 - 如果一个线程锁住了一个互斥量,那么可以保证的是在该线程释放这个锁之前没有其他线程可以锁住这个互斥量;
  • 非忙等待 - 如果一个线程(线程1)尝试去锁住一个由线程2锁住的锁,线程1会挂起(suspend)并且不会消耗任何CPU资源,直到线程2释放了这个锁。这时,线程1会唤醒并继续执行,锁住这个互斥量。

2. 关于生命周期

通过 [NSThread exit] 方法使线程退出 ,NSThread 是可以立即终止正在执行的任务(可能会造成内存泄露,这里不深究)。甚至你可以在主线程中执行该操作,会使主线程也退出,app 无法再响应事件。而 cancel 可以通过作为标志位来达到类似目的,如果不做任何处理,仍然会继续执行。

GCD和NSOperationQueue可以取消队列中未开始执行的任务,对于已经开始执行的任务就无能为力了。

5af682f88744c7c5f4b9f5968535c3aa.png

3. 并行与并发

看到很多「看我就够了」系列文章里提到 并发队列 ,这里有一个小陷阱,混淆了 并发并行 的概念。我们先来看看一下他们之间的区别:

c826268961bf6248572cc227a9d072fe.png

并发与并行

从图中可以看到,并行才是真正的多线程,而并发只是在多任务中切换。一般多核CPU可以并行执行多个线程,而单核CPU实际上只有一个线程,多路复用达到接近同时执行的效果。在 iOS 中 concurrentQueue 和 globalQueue 从 Xcode 中线程使用情况来看,都达到了并行的效果,虽然名字是并发。

4. 队列与线程

队列是保存以及管理任务的,将任务加到队列中,任务会按照加入到队列中先后顺序依次执行。如果是全局队列和并行队列,则系统会根据系统资源去创建新的线程去处理队列中的任务,线程的创建、维护和销毁由操作系统管理,还有队列本身是线程安全的。

使用 NSOperationQueue 实现多线程的时候是可以控制线程总数及线程依赖关系的,而 GCD 只能选择并行或者串行队列。

资源竞争

多线程同时执行任务能提高程序的执行效率和响应时间,但是多线程不可避免地遇到同时操作同一资源的情况。前段时间看到的一个资源竞争的问题为例:

@property (nonatomic, strong) NSString *target;
dispatch_queue_t queue = dispatch_queue_create("parallel", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 1000000 ; i++) {
dispatch_async(queue, ^{
self.target = [NSString stringWithFormat:@"ksddkjalkjd%d",i];
});
}

解决办法:

  • @property (nonatomic, strong) NSString *target;将nonatomic改成atomic。
  • 将并行队列 DISPATCH_QUEUE_CONCURRENT 改成串行队列 DISPATCH_QUEUE_SERIAL。
  • 异步执行dispatch_async 改成同步执行dispatch_sync。
  • 赋值使用@synchronized 或者上锁。

这些方法都是从避免同时访问的角度来解决该问题,有更好的方法欢迎分享。

花样死锁

任何事情都有两面性,就像多线程能提升效率的同时,也会造成资源竞争的问题。而锁在保证多线程的数据安全的同时,粗心大意之下也容易发生问题,那就是 死锁

1. NSOperationQueue

鉴于 NSOperationQueue 高度封装,使用起来非常简单,一般不会出什么幺蛾子,下面的案例展示了一个不好示范,通常我们通过控制 NSOperation 之间的从属关系,来达到有序执行任务的效果,但是如果互相从属或者循环从属都会造成所有任务无法开始。

NSBlockOperation *blockOperation1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"lock 1 start");
[NSThread sleepForTimeInterval:1];
NSLog(@"lock 1 over");
}];
NSBlockOperation *blockOperation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"lock 2 start");
[NSThread sleepForTimeInterval:1];
NSLog(@"lock 2 over");
}];
NSBlockOperation *blockOperation3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"lock 3 start");
[NSThread sleepForTimeInterval:1];
NSLog(@"lock 3 over");
}];
// 循环从属
[blockOperation2 addDependency:blockOperation1];
[blockOperation3 addDependency:blockOperation2];
[blockOperation1 addDependency:blockOperation3]; // 循环的罪魁祸首
// 互相从属
//[blockOperation1 addDependency:blockOperation2];
//[blockOperation2 addDependency:blockOperation1];
[_operationQueue addOperation:blockOperation1];
[_operationQueue addOperation:blockOperation2];
[_operationQueue addOperation:blockOperation3];

有没有人试过下面这种情况,如果好奇就试试吧!

[blockOperation1 addDependency:blockOperation1];

2. GCD

大多数开发者都知道在主线程里同步执行任务会造成死锁,一起来看看还有哪些情况下会造成死锁或类似问题。

a. 在主线程同步执行 造成 EXC_BAD_INSTRUCEION 错误:

- (void)deadlock1 {
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"task 1 start");
[NSThread sleepForTimeInterval:1.0];
NSLog(@"task 1 over");
});
}

b. 和主线程同步执行类似,在串行队列中嵌套使用同步执行任务,同步队列 task1 执行完成后才能执行 task2 ,而 task1 中嵌套了task2 导致 task1 注定无法完成。

- (void)deadlock2 {
dispatch_queue_t queue = dispatch_queue_create("com.xietao3.sync", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{ // 此处异步同样会造成互相等待
NSLog(@"task 1 start");
dispatch_sync(queue, ^{
NSLog(@"task 2 start");
[NSThread sleepForTimeInterval:1.0];
NSLog(@"task 2 over");
});
NSLog(@"task 1 over");
});
}

嵌套同步执行任务确实很容易出 bug ,但不是绝对,将同步队列DISPATCH_QUEUE_SERIAL 换成并行队列 DISPATCH_QUEUE_CONCURRENT 这个问题就迎刃而解。修改成并行队列后案例中 task1 仍然要先执行完嵌套在其中的 task2 ,而 task2 开始执行时,队列会另起一个线程执行 task2 , task2 执行完成后 task1 继续执行。

c. 在很多人印象中,异步执行不容易发生互相等待的情况,确实,即使是串行队列,异步任务会等待当前任务执行后再开始,除非你加了一些不健康的佐料。

- (void)deadlock3 {
dispatch_queue_t queue = dispatch_queue_create("com.xietao3.asyn", DISPATCH_QUEUE_SERIAL);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_async(queue, ^{
__block NSString *str = @"xietao3"; // 线程1 创建数据
dispatch_async(queue, ^{
str = [NSString stringWithFormat:@"%ld",[str hash]]; // 线程2 加工数据
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"%@",str); // 线程1 使用加工后的数据
});
}

d. 常规死锁,在已经上锁的情况下再次上锁,形成彼此等待的局面。

if (!_lock) _lock = [NSLock new];
dispatch_queue_t queue = dispatch_queue_create("com.xietao3.sync", DISPATCH_QUEUE_CONCURRENT);
[_lock lock];
dispatch_sync(queue, ^{
[_lock lock];
[NSThread sleepForTimeInterval:1.0];
[_lock unlock];
});
[_lock unlock];

要解决也比较简单,将NSLock换成递归锁NSRecursiveLock,递归锁就像普通的门锁,顺时针转一圈加锁后,逆时针一圈即解锁;而如果顺时针两圈,同样逆时针两圈即可解锁。下面来一个递归的例子:

// 以下代码可以理解为顺时针转10圈上锁,逆时针转10圈解锁

- (void)recursivelock:(int)count {
if (count>10) return;
count++;
if (!_recursiveLock) _recursiveLock = [NSRecursiveLock new];
[_recursiveLock lock];
NSLog(@"task%d start",count);
[self recursivelock:count];
NSLog(@"task%d over",count);
[_recursiveLock unlock];
}

3. 其他

除了上面提到的互斥锁和递归锁,其他的锁还有:

  • OSSpinLock(自旋锁)
  • pthread_mutex(OC中锁的底层实现)
  • NSConditionLock(条件锁,对于新手更容易产生死锁)
  • NSCondition(条件锁的底层实现)
  • @synchronized(对象锁)

大部分锁触发死锁的情况和互斥锁基本一致,NSConditionLock使用起来会更加灵活,而自旋锁虽然性能爆表,但是存在漏洞,希望了解更多关于锁的知识可以点这里,在看的同时不要忘记亲自动手验证一下,边看边写边验证,记得更加深刻。

总结

关于多线程、锁的文章已经烂大街了,本文尽可能地从新的角度来看问题,尽量不写那些重复的内容,希望对你有所帮助,如果文中内容有误,欢迎指出。

推荐

2020大厂面试题

关于Flutter的面试题

20道经典面试题

点击进群密码:111 更多面试资料等你来拿 更多技术等你来探讨

620427d88e6cc3388788dc5b391b72c1.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/529264.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

学校为什么要单位接收函_学校、小区运动场为什么要选择塑胶跑道

随着人们生活水平的不断提高&#xff0c;科技的日新月异&#xff0c;各行各业都向着更加健康环保安全舒适的方向迈进。就拿小区、学校的运动场所地坪为例&#xff0c;传统的“沙土跑道”已经被运动塑胶跑道所替代。那么运动场为什么会选择塑胶跑道呢&#xff1f; 塑胶跑道又称全…

pitstop插件使用说明_【学员分享】程序员效率神器,最常用VIM插件安装大全

相信大家多次被推荐用vim作为编辑程序&#xff0c;知道vim编辑有很多优点&#xff0c;但是vim初始界面太原始了&#xff0c;安装了之后只能用来编辑&#xff0c;如果要运行就需要退出去运行&#xff0c;麻烦死了。回想用现成的IDE是多么的舒服。但是为了更好的学习&#xff0c;…

JVM思维导图、正则表达式符号图、企业内部开发流程图

JVM思维导图、正则表达式符号图、企业内部开发流程图 1.JVM思维导图&#xff1a; 2.正则表达式符号图&#xff1a; 3.企业内部开发流程图&#xff1a;

萧县机器人_全国总决赛第一名!萧县杨楼的这位学生厉害了

&#xfeff; 提示&#xff1a;点击上方"萧县关注"↑免费订阅本刊点击上方关注我们&#xff0c;免费订阅更多精彩内容&#xfeff;&#xfeff;&#xfeff;&#xfeff;&#xfeff;&#xfeff;&#xfeff;&#xfeff;&#xfeff;&#xfeff;&#xfeff;&…

关于JSP页面无法加载css,游览器访问jsp页面样式未生效导致乱序

关于JSP页面无法加载css&#xff0c;游览器访问jsp页面样式未生效导致乱序 1.修改自己过滤器中对编码格式的修改 如图&#xff1a; 代码如下&#xff1a; public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOE…

将视图转为image_JavaScript二进制数组(2)TypedArray视图

ArrayBuffer对象作为内存区域可以存放多种类型的数据。同一段内存&#xff0c;不同数据有不同的解读方式&#xff0c;这种解读方式称为“视图&#xff08;view&#xff09;”。ArrayBuffer有两种类型的视图&#xff0c;一种是类型化数组视图&#xff08;TypedArray&#xff09;…

解决IDEA中maven工程的jsp、jstl依赖导入了 ,但是 jsp页面的uri却不提示(手动输上也报红)

解决IDEA中maven工程的jsp、jstl依赖导入了 &#xff0c;但是 jsp页面的uri却不提示&#xff08;手动输上也报红&#xff09; 出现原因&#xff1a;idea内有缓存 解决办法&#xff1a;File --> Invalidate Caches / Restart… --> lnvalidate and Restart idea版本&#…

空格 过滤多个_CAD选择过滤器的运算符如何使用?

选择过滤器FILTER在CAD早期版本中是扩展工具的一个功能&#xff0c;到了高版本变成标配的功能&#xff0c;但在浩辰CAD的菜单或工具面板中我还找到选择过滤器的命令。浩辰CAD面板、右键菜单和特性面板倒是都提供了快速选择的功能&#xff0c;快速选择功能应该是借鉴选择过滤器开…

各层作用_终于弄明白了 Singleton,Transient,Scoped 的作用域是如何实现的

一&#xff1a;背景1. 讲故事前几天有位朋友让我有时间分析一下 aspnetcore 中为什么向 ServiceCollection 中注入的 Class 可以做到 Singleton&#xff0c;Transient&#xff0c;Scoped&#xff0c;挺有意思&#xff0c;这篇就来聊一聊这一话题&#xff0c;自从 core 中有了 S…

权限管理系统_在Gitee狂揽11K Star!这个SpringCloud的权限管理系统你必须知道

SpringCloud 大家都很熟悉了&#xff0c;它作为一套完整的微服务解决方案&#xff0c;广受 Java 开发者们的好评&#xff0c; 今天就为大家介绍一款 Gitee 上的王牌项目&#xff0c;基于 SpringCloud 的权限管理系统——Pig。项目名称&#xff1a;Pig项目作者&#xff1a;pig4c…

导出排除的表_excel拆分实例:如何快速制作考勤统计分析表

编按&#xff1a;面对新的统计需求&#xff0c;很多人会一下变懵&#xff0c;不知如何办。如果涉及的统计有一千多行数据&#xff0c;哭的心思都有了&#xff1a;什么时候才能下班哟&#xff01;今天老菜鸟通过考勤统计分析表实例分享自己面对新统计需求的解决方法&#xff1a;…

python 数据字典用法_python数据字典的操作

一、什么是字典&#xff1f;字典是Python语言中唯一的映射类型。映射类型对象里哈希值(键&#xff0c;key)和指向的对象(值&#xff0c;value)是一对多的的关系&#xff0c;通常被认为是可变的哈希表。字典对象是可变的&#xff0c;它是一个容器类型&#xff0c;能存储任意个数…

双系统安装deepin20_win10deepin15.10双系统安装教程

第二步&#xff1a;下载深度启动盘制作工具深度启动盘制作工具地址第三步&#xff1a;制作U盘启动盘打开第二部下载的启动盘制作工具&#xff0c;并准备一个u盘插入待装系统的电脑&#xff0c;选择镜像文件后&#xff0c;下一步选择磁盘并勾选格式化磁盘&#xff0c;点下一步开…

ubuntu19 安装git_在Ubuntu 18.04上安装Git

步骤1.首先&#xff0c;通过运行以下命令确保您的系统和apt包列表完全更新&#xff1a;apt-get update -yapt-get upgrade -y第2步。在Ubuntu 18.04上安装Git。现在让我们安装git&#xff1a;apt install git您可以使用以下命令来检查已安装的git版本&#xff1a;$ git --versi…

mysql数据库开发环境_MacOS下搭载开发环境之数据库篇(Mysql + Navicat)

一、安装Mysql1、官网下载mysql的tar包(提示&#xff1a;建议vpn环境下载)2、解压并安装tar包# 移动解压后的二进制包到安装目录sudo mv mysql-5.7.19-osx10.9-x86_64 /usr/local/mysql# 更改 mysql 安装目录所属用户与用户组cd /usr/localsudo chown -R root:wheel mysql# 初始…

mysql alter 唯一键_MySQL列属性 之 唯一键

MySQL列属性 之 唯一键唯一键唯一键&#xff1a;每张表往往有多个字段需要具有唯一性&#xff0c;数据不能重复&#xff0c;但是在每张表中&#xff0c;只能有一个主键&#xff0c;因此 唯一键就是用来解决表中多个字段需要具有唯一性的问题。例如身份证号码应该每一行的记录不…

wpf 使用位图画图为什么断断续续_WPF的未来是微软WinUi!

WPF(Windows Presentation Foundation)是微软推出的基于Windows 的用户界面框架&#xff0c;属于.NET Framework 3.0的一部分。它提供了统一的编程模型、语言和框架&#xff0c;真正做到了分离界面设计人员与开发人员的工作&#xff1b;同时它提供了全新的多媒体交互用户图形界…

antd新增一行页码不正确_antd-Table@4.x对rowKey属性的重构

时间&#xff1a;2020/04/26 &#xff0c;转载请注明出处。写在前面antd团队于2020年2月发布了酝酿已久的antd4.0版本&#xff0c;对样式的调整、部分组件逻辑的重构都进行了较大改动&#xff0c;本文针对Table的rowKey属性重构作分析。由一个mistake带来的思考在数据治理模块的…

pandas mysql index_Pandas从入门到精通(3)- Pandas多级索引MultiIndex

首先了解一下什么是多级索引&#xff0c;以及它的作用&#xff0c;为什么要有这个玩意。多级索引也称为层次化索引(hierarchical indexing)&#xff0c;是指数据在一个轴上(行或者列)拥有多个(两个以上)索引级别。之所以引入多级索引&#xff0c;在于它可以使用户能以低维度形式…

tensorflow 启动多个session_Tensorflow源码解析7 -- TensorFlow分布式运行时

1 概述TensorFlow架构设计精巧&#xff0c;在后端运行时这一层&#xff0c;除了提供本地运行时外&#xff0c;还提供了分布式运行时。通过分布式训练&#xff0c;在多台机器上并行执行&#xff0c;大大提高了训练速度。前端用户通过session.run()启动系统执行时&#xff0c;tar…