【iOS】多线程 锁问题总结

文章目录

  • 前言
    • 1. 你理解的多线程
      • 优点
      • 缺点
    • 2. atomic 和 nonatomic 的区别及其作用
    • 3. GCD的队列类型 - 三种队列类型
    • 4. GCD的死锁问题
    • 5. 多线程之间的区别和联系
    • 6. 进程和线程?
        • 进程间的通信方式
        • 线程间的通信方式
    • 6. iOS的线程安全手段如何保证

前言

iOS 锁和多线程的总结

1. 你理解的多线程

多线程是同时执行多个线程(子任务)的能力,用于提高程序性能和响应性。它允许在一个程序中并发地处理多个任务。

  • 并发:在一个时间段多个线程同时进行,计算机通过切换不同的线程实现多线程任务。

优点

  1. 大大提高了程序的运行速度。
  2. 使用线程可以把占据时间较长的任务放到后面去处理,从而提升用户的体验。

缺点

  1. 如果有大量的线程,会影响性能,因为操作系统需要在它们之间切换。
  2. 更多的线程需要更多的内存空间。

2. atomic 和 nonatomic 的区别及其作用

  1. atomic原子操作:加锁,保证setter和getter存取方法的线程安全(仅仅对setter和getter方法加锁)。因为线程加锁,别的线程访问当前属性的时候会先执行完属性当前的操作。
  • 对同一对象的set和get的操作是顺序执行的。
  • 速度不快,因为要保证操作整体完成。
  • 线程安全,需要消耗大量系统资源为属性加锁。
  • 使用atomic并不能保证绝对的线程安全,因为atomic仅仅是对系统生成的的settergetter方法加锁, 对于绝对保证线程安全的操作,需要使用更高级的方式处理,NSSpinLock, @syncronized 锁保证线程安全。
  1. nanatomic非原子操作,不加锁,线程执行快,但是多个线程访问同一个属性可能产生crash。
  • 不是默认的
  • 速度更快,如果有两个线程访问同一个属性可能造成crash。
  • 非线程安全
  1. atomic与nonatom的主要区别就是系统自动生成的getter/setter方法不一样
  • atomic系统自动生成的getter/setter方法会进行加锁操作。
  • nonatomic系统自动生成的getter/setter方法不会进行加锁操作。

⚠️:atomic修饰的属性,系统生成的 getter/setter 会保证 getset 操作的完整性,不受其他线程影响。比如,线程 A 的 getter 方法运行到一半,线程 B 调用了 setter:那么线程 A 的 getter 还是能得到一个完好无损的对象。

3. GCD的队列类型 - 三种队列类型

  1. The main queue(主线程串行队列)与主线程功能相同,提交到main queue的任务会在主线程中执行。
dispatch_get_main_queue() 来获取
  1. Global queue(全局并发队列) 全局并发队列由整个进程共享,有 高 中(默认是中) 低和后台四个优先级别。
dispatch_get_global_queue() 可以设置优先级
  1. Custom queue(自定义队列) 可以串行,也可以并发。
dispatch_queue_create()

4. GCD的死锁问题

概念:所谓死锁,通常是两个线程A和B都卡住了,A在等B,B在等A,互相等待到值死锁。

  1. .主线程串行队列同步执行任务,在主线程运行时,会产生死锁
NSLog(@"1"); // 任务1
dispatch_sync(dispatch_get_main_queue(), ^{
    NSLog(@"2"); // 任务2
});
NSLog(@"3"); // 任务3

分析:

  • dispatch_sync表示是一个同步线程;
  • dispatch_get_main_queue表示运行在主线程中的主队列;
  • 任务2是同步线程的任务。
  • 任务3需要等待任务2结束之后再执行.

为什么造成死锁?

  1. 首先执行任务1,这是肯定没问题的,只是接下来,程序遇到了同步线程,那么它会进入等待,等待任务2执行完,然后执行任务3。但这是主队列,是一个特殊的串行队列,有任务来,当然会将任务加到队尾,然后遵循FIFO原则执行任务。那么,现在任务2就会被加到最后,任务3排在了任务2前面

任务3要等任务2执行完才能执行,任务2又排在任务3后面,意味着任务2要在任务3执行完才能执行,所以他们进入了互相等待的局面。【既然这样,那干脆就卡在这里吧】这就是死锁。

请添加图片描述

  1. 同步异步互相嵌套
// 同步 + 异步 互相嵌套产生死锁
- (void)sync_async {
    dispatch_queue_t queue = dispatch_queue_create("com.demo.serialQueue", DISPATCH_QUEUE_SERIAL);
    NSLog(@"1"); // 任务1
    dispatch_async(queue, ^{
        NSLog(@"2"); // 任务2
        dispatch_sync(queue, ^{
            NSLog(@"3"); // 任务3
        });
        NSLog(@"4"); // 任务4
    });
    NSLog(@"5"); // 任务5
}

请添加图片描述
分析:首先通过自定义队列创建了dispatch_queue_create函数创建了一个DISPATCH_QUEUE_SERIAL的串行队列。

  1. 执行任务1.
  2. 遇到异步线程,将【任务2、同步线程、任务4】加入串行队列中。因为是异步线程,所以在主线程中的任务5不必等待异步线程中的所有任务完成;
  3. 因为任务5不必等待,所以2和5的输出顺序不能确定;
  4. 任务2执行完以后,遇到同步线程,这时,将任务3加入串行队列;
  5. 又因为任务4比任务3早加入串行队列,所以,任务3要等待任务4完成以后,才能执行。但是任务3所在的同步线程会阻塞,所以任务4必须等任务3执行完以后再执行。这就又陷入了无限的等待中,造成死锁。
    在这里插入图片描述
    主线程无限循环
- (void)async_loop {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"1"); // 任务1
        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"2"); // 任务2
        });
        NSLog(@"3"); // 任务3
    });
    NSLog(@"4"); // 任务4
    while (1) {
    }
    NSLog(@"5"); // 任务5
    
    // a打印 4 1 / 1 4 顺序不定   
}

打印结果: 4 1 / 1 4 顺序不定

分析:

  • 先来看看都有哪些任务加入了Main Queue:【异步线程、任务4、死循环、任务5】。
  • 在加入到Global Queue异步线程中的任务有:【任务1、同步线程、任务3】。
  • 第一个就是异步线程,任务4不用等待,所以结果任务1和任务4顺序不一定。
  • 任务4完成后,程序进入死循环,Main Queue阻塞。但是加入到Global Queue的异步线程不受影响,继续执行任务1后面的同步线程。
  • 同步线程中,将任务2加入到了主线程,并且,任务3等待任务2完成以后才能执行。这时的主线程,已经被死循环阻塞了。所以任务2无法执行,当然任务3也无法执行,在死循环后的任务5也不会执行。

最终,只能得到1和4顺序不定的结果。

5. 多线程之间的区别和联系

在这里插入图片描述

GCD和NSOperation

  • GCD的执行效率更高,执行的是由Block构成的任务,是一个轻量级的数据结构,写起来更加方便
  • GCD只支持FIFO队列,NSOperationQueue可以通过设置最大并发数、设置优先级、添加依赖关系来调整执行顺序
  • NSOperation可以跨越队列设置依赖关系,GCD仅仅能通过栅栏等方法才能控制执行顺序
  • NSOperation更加面向对象,支持KVO,也可以通过继承等关系添加子类。
  • 所以如果我们需要考虑异步操作之间的顺序行、依赖关系,比如多线程并发下载等等,就使用NSOperation

GCD 与 NSThread 的区别

  • NSThread 通过 @selector 指定要执行的方法,代码分散, 依靠的是NSObject的分类实现的线程之间的通讯,如果要开线程必须创建多个线程对象。经常只用的是[NSTread current] 查看当前的线程。
  • NSThread是一个控制线程执行的对象,它不如NSOperation抽象,通过它我们可以方便的得到一个线程,并控制它。但NSThread的线程之间的并发控制,是需要我们自己来控制的,可以通过NSCondition实现。
  • GCD 通过 block 指定要执行的代码,代码集中, 所有的代码写在一起的,让代码更加简单,易于阅读和维护,不需要管理线程的创建/销毁/复用的过程!程序员不用关心线程的生命周期

6. 进程和线程?

参考:进程和线程的概念、区别及进程线程间通信
1. 基本概念:

  • 进程是对运行时程序的封装,是系统进行资源调度和分配的基本单位,实现了操作系统的并发。
  • 线程是进程的子任务,是CPU调度和分配的基本单位,用于保障程序执行的实时性,实现进程内部的并发。线程是操作系统可以识别的最小执行和调度单位。每个线程都肚子占用一个虚拟处理器等,每个线程完成不同的任务,但是共享同一地址空间(也就是同样的动态内存,映射文件,目标代码等等),打开的文件队列和其他内核资源。

2. 区别:

  • 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程依赖于进程而存在。
  • 进程是资源分配的最小单位,线程是CPU调度的最小单位
  • 进程在执行过程中拥有独立的内存单元,而多个线程共享进程的内存。(资源分配给进程,同一进程的所有线程共享该进程的所有资源。同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量和临时变量。)
  • 进程间不会相互影响 ;线程:一个线程挂掉将导致整个进程挂掉

3. 通信方式

进程间的通信方式

  1. 进程间通信主要包括管道、系统IPC(包括消息队列、信号量、信号、共享内存等)、以及套接字socket。
  2. 信号量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器,可以用来控制多个进程对共享资源的访问。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。

线程间的通信方式

  1. 临界区:通过多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问;
  2. 互斥量Synchronized/Lock采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问
  3. 信号量Semphare:为控制具有有限数量的用户资源而设计的,它允许多个线程在同一时刻去访问同一个资源,但一般需要限制同一时刻访问此资源的最大线程数目。
  4. 事件(信号),Wait/Notify:通过通知操作的方式来保持多线程同步,还可以方便的实现多线程优先级的比较操作进程间通信的方式:

6. iOS的线程安全手段如何保证

参考:iOS中有哪些技术可以保证线程安全?
问:1块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源,比如多个线程访问同一个对象、同一个变量、同一个文件。当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题。此时,我们需要用线程锁来解决。

线程数据安全的方法:

  1. natomic原子操作:使用atomic多线程原子性控制,atomic的原理给setter加上锁,getter不会加锁。
  2. 使用GCD实现atomic操作:给某字段的setter方法和getter方法加上同步队列;
- (void)setCount:(NSInteger)newcount
{
    dispatch_sync(_synQueue, ^{
         count = newcount;
    });
}
- (NSInteger)count
{
     __block NSInteger localCount;
     dispatch_sync(_synQueue, ^{
          localCount = count;
     });
     return localCount;
}
  • 互斥锁能够有效的防止因多线程抢夺资源造成的数据安全问题,但是需要消耗大量的CPU资源。
  1. 互斥锁: 使用互斥锁可以确保同一时间只有一个线程访问共享资源。例如@synchronized创建互斥锁
@synchronized (self) {
    // 访问共享资源的代码
}

  1. 自旋锁:自旋锁(Spin Lock):自旋锁一种忙等待的锁,它会不断地尝试获取锁,直到成功为止。在Objective-C中,可以使用os_unfair_lock来创建自旋锁。
  2. 信号量(Semaphore):信量是一种数器,用于控制同时访问某个资源的线程数量。
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
// 访问共享资源的代码
dispatch_semaphore_signal(semaphore);

  1. 串行队列:串行队列(Serial Queue):使用串行队列可以确保任务按顺序执行,从而避多个线程同时访问共享资源。可以使用GCD(Grand Central Dispatch)来创建串行队列。
dispatch_queue_t serialQueue = dispatch_queue_create("com.example.serialQueue DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue, ^{
    // 访问共享资源的代码
});

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

HCIP——BGP基础

BGP 一、BGP基础二、BGP的发展历史三、BGP在企业中的应用四、距离矢量型协议和路径矢量型协议五、BGP的特征六、BGP的对等关系七、BGP的数据包八、BGP的状态机九、BGP的工作过程十、BGP的路由黑洞十一、BGP的环路问题EBGP水平分割IBGP水平分割 十二、BGP的基本配置1、BGP的对等…

【机器学习】Classification using Logistic Regression

Classification using Logistic Regression 1. 分类问题2. 线性回归方法3. 逻辑函数(sigmod)4.逻辑回归5. 决策边界5.1 数据集5.2 数据绘图5.3 逻辑回归与决策边界的刷新5.4 绘制决策边界 附录 导入所需的库 import numpy as np %matplotlib widget imp…

开源代码分享(9)—面向100%清洁能源的发输电系统扩展规划(附matlab代码)

1.背景介绍 1.1摘要 本文提出了一种新颖的建模框架和基于分解的解决策略,将随机规划(SP)和鲁棒优化(RO)相结合,以应对协调中长期电力系统规划中的多重不确定性。从独立系统运营商(ISO&#xff…

测试|性能测试相关理论

测试|性能测试相关理论(了解) 文章目录 测试|性能测试相关理论(了解)1.什么是性能测试生活中遇到的软件性能问题:性能测试定义:性能测试和功能测试有什么区别:性能好坏的评价指标影响一个软件性…

Redis安装部署(基于windows平台)

redis简介 键值对存储数据库是NoSQL数据库的一种类型,也是最简单的NoSQL数据库。顾名思义,键值对存储数据库中的数据是以键值对的形式来存储的。常见的键值对存储数据库有Redis、Tokyo Cabinet/Tyrant、Voldemort以及Oracle BDB数据库。 Remote Diction…

一起学算法(二维数组篇)

1.概念定义 1.矩阵的定义 矩阵A(nm)的定义时按照长方形排列的复数或实数集合,其中n代表的是行数,m代表的是列数。如下所示,代表的是一个4x3的矩阵 在Java中,我们可以用A[n][m]来代表一个n*m的矩阵,其中A[i][j]代表的是…

2023年的深度学习入门指南(23) - ChatGLM2

2023年的深度学习入门指南(23) - ChatGLM2 在《在你的电脑上运行大模型》这一节,我们曾经介绍过ChatGLM模型,它是当时最好的中文大模型之一。现在,它又更新到了第二代,即ChatGLM2。 当时,我们的技术储备还不足&#…

upload-labs详解------持续更新

目录 注: 搭建: pass-01(前端绕过) pass-02(后缀绕过) pass-03(黑名单绕过) pass-04(Apache解析漏洞\.htaccess文件绕过) 注: 本项目提供的…

Halcon学习之一维测量实战之测量矩形(一)

一、采集图像 (1)测量充电器 测量充电器的引脚,然后每次旋转充电器,让测量矩形都跟着它转,这就是定位+测量, (2)测量钥匙 (3)测量瓶盖 我们后面还会涉及到拟合的问

【Python】Web学习笔记_flask(1)——模拟登录

安装flask pip3 install flask 第一部分内容&#xff1a; 1、主页面输出hello world 2、根据不同用户名参数输出用户信息 3、模拟登录 from flask import Flask,url_for,redirectappFlask(__name__)app.route(/) def index():return hello worldapp.route(/user/<uname…

IDEA中连接虚拟机 管理Docker

IDEA中连接虚拟机 管理Docker &#x1f4d4; 千寻简笔记介绍 千寻简笔记已开源&#xff0c;Gitee与GitHub搜索chihiro-notes&#xff0c;包含笔记源文件.md&#xff0c;以及PDF版本方便阅读&#xff0c;且是用了精美主题&#xff0c;阅读体验更佳&#xff0c;如果文章对你有帮…

阻塞队列BlockingQueue详解

一、阻塞队列介绍 1、队列 队列入队从队首开始添加&#xff0c;直至队尾&#xff1b;出队从队首出队&#xff0c;直至队尾&#xff0c;所以入队和出队的顺序是一样的 Queue接口 add(E) &#xff1a;在指定队列容量条件下添加元素&#xff0c;若成功返回true&#xff0c;若当前…

Flask简介与基础入门

一、了解框架 Flask作为Web框架&#xff0c;它的作用主要是为了开发Web应用程序。那么我们首先来了解下Web应用程序。Web应用程序 (World Wide Web)诞生最初的目的&#xff0c;是为了利用互联网交流工作文档。 1、一切从客户端发起请求开始。 所有Flask程序都必须创建一个程序…

webScoket

webScoket是什么&#xff1f; 支持端对端通讯可以由客户端发起&#xff0c;也可以有服务端发起用于消息通知、直播间讨论区、聊天室、协同编辑等 做一个简单的webScoket 客户端配置&#xff1a; 1、新建一个页面叫web-scoket.html <!DOCTYPE html> <html lang"…

【CSS】ios上fixed固定定位的input输入框兼容问题

需求 &#xff1a; 实现一个简单的需求&#xff0c;上方是搜索框并且固定顶部&#xff0c;下方是滚动的内容list 问题 : 若如图上方使用固定定位, 下方用scroll-view, 在安卓上是没有问题的, 但是发现ios上会出现兼容问题 : 问题1: 当content list滚动到中间时再去搜索, 展…

maven引入本地jar包的简单方式【IDEA】【SpringBoot】

前言 想必点进来看这篇文章的各位&#xff0c;都是已经习惯了Maven从中央仓库或者阿里仓库直接拉取jar包进行使用。我也是&#x1f921;&#x1f921;。 前两天遇到一个工作场景&#xff0c;对接三方平台&#xff0c;结果对方就是提供的一个jar包下载链接&#xff0c;可给我整…

明明已经安装字体,但IDEA、CLION无法找到思源黑体/Source Hans Sans的问题解决

IDEA、CLION的Jetbrain系列软件不支持非TrueType的中文字体&#xff0c;而Adobe官方给出的字体却不是TrueType的&#xff0c;所以便会导致Jetbrain系软件无法找到已安装的中文字体&#xff0c;因此我们需要安装TrueType的字体 请在以下Github链接中下载&#xff1a; TrueType思…

java实现钉钉群机器人@机器人获取信息后,机器人回复

1.需求 鉴于需要使用钉钉群机器人回复&#xff0c;人们提出的问题&#xff0c;需要识别提出的问题中的关键词&#xff0c;后端进行处理实现对应的业务逻辑 2.实现方式 用户群机器人&#xff0c;附带提出的问题&#xff0c;后端接收消息后识别消息内容&#xff0c;读取到关键…

使用Three.js创建旋转的立方体

使用Three.js创建旋转的立方体 在本篇技术博客中&#xff0c;我们将介绍如何使用Three.js创建一个简单的场景&#xff0c;其中包含一个旋转的立方体。我们将学习如何设置场景、摄像机、立方体和渲染器&#xff0c;以及如何使用OrbitControls和gsap库来实现立方体的旋转动画和交…