philm-iOS-wiki
  • 介绍
  • 网络层
    • 说明
  • UI
    • 说明
    • 在ios7以前使用ColorSpace的坑
    • UITableView偏移异常问题
    • drawRect时单独设置文字阴影无效
    • Xcode9下相册访问权限问题
    • 避免同时点击多个Button
    • scroll上的button延迟响应问题
    • uibutton触发边界事件
    • ios 11 上tableview 改动
    • YYImage 显示指定区域的图片
  • 数据持久化
    • 说明
  • 其它
    • 取消延迟执行之坑
    • NSString 转换 float 的精度问题
  • 每周阅读
    • 目录
    • 深入思考NSNotification
    • gitBook使用小助手
    • iOS App签名的原理
    • 响应链
    • iOS10跳转系统到设置页
    • SDWebImage下载高清图内存问题
    • iOS圆角避免离屏渲染
    • 常用的延时调用
    • iOS 神经网络
    • SDWebImage缓存策略
    • 3Dtouch
    • 为什么 Objective-C 对象存储在堆上而不是栈上
    • 深入浅出理解视频编码H264结构
    • CATextLayer学习
    • cocoaPods
    • 任意网站支持RSS
    • Metal简介
    • 动态更改icon
    • CAReplicatorLayer
    • 增加点击间隔
    • 勒索病毒当道的时代
    • iOS常用宏定义
    • Metal实现YUV转RGB渲染视频
    • 获取当前下载的app及下载进度
    • OpenGL ES 三种类型修饰 uniform attribute varying
    • 技术部门引入OKR
    • 基于runloop的线程保活、销毁与通信
    • 深入理解哈希表
    • TOLL-FREE BRIDGING 和 UNMANAGED
    • 开发者能拿到的标识符
    • Swift自定义LOG
    • 系统通知整理
    • iOS 中的 imageIO 与 image 解码
    • CGImageRef基本介绍及方法说明
    • Swift 3.0 语法
    • webview加载部分网页
    • 在CAAnimation中暂停动画
    • 把代码迁移到协调器上
    • ios11API更新整理
    • 非越狱iOS设备的远程控制实现原理
    • 关于本地化
    • swift命名空间
    • CoreML与coremltools体验
    • 力学动画
    • Swift 4官方文档中文版: The Basic(上)
    • swift 中的KVO用法
    • GPUImage的图像形变设计(简单形变部分)
    • iOS响应式架构
    • 移动端图片上传旋转、压缩的解决方案
    • AVFoundation使用指南AVAssert使用
    • 过渡动画
    • 谈谈 MVX 中的 Model
    • AVFoundation编程-AVPlayer使用
    • GPUImage的图像形变设计(复杂形变部分)
    • What's New in LLVM 9
    • ios的事件机制
    • GPUImage源码解读(一)
    • GPUImage源码解读(二)
    • iOS 启动优化
    • 模块化 Swift 中的状态
    • swift中的let和var背后的编程模式
    • Swift Runtime动态性分析
    • RAC下的响应式编程
    • GPUImage源码解读(三)
    • 如何准确判断webView是否加载完成
    • NSObject的+load和+initialize详解
    • ios8以后设置启动图
    • GPUImage源码解读(四)
    • Swift自动闭包
    • IOS11新特性
    • GPUImage源码解读(五)
    • 理解 OC 内部的消息调用、消息转发、类和对象
    • 修饰符
    • IOS 切面统计事件解耦
    • GPUImage源码解读(六)
    • CoreImage介绍
    • 影响Core Animation性能的原因
    • Instruments中的动画工具选项介绍
    • GPUImage源码解读(七)
    • Xcode 7新的特性Lightweight Generics 轻量级泛型与__kindof修饰符
    • GPUImage源码解读(八)
    • Core Image之自定 Filter
    • iOS通用链接
    • 谈nonatomic非线程安全问题
    • 深拷贝与浅拷贝
    • CIKernel 介绍
    • iOS11适配
    • GPUImage源码解读(九)
    • CVPixelBufferCreate使用的坑
    • ios一窥并发底层
    • ARKit进阶:物理世界
    • ARKit的工作原理及流程介绍
    • UI线程卡顿监控
    • FBKVOController使用
    • GPUImage源码解读(十)
    • WKWebView在ios11崩溃问题解决方法
    • 微信iOS SQLite源码优化实践
    • HEIF 和 HEVC 研究
    • 谈谈 iOS 中图片的解压缩
    • 提升 iOS 开发效率! Xcode 9 内置模拟器的9个技巧
    • ObjC和JavaScript的交互,在恰当的时机注入对象
    • iOS数据保护
    • iOS11中网络层的一些变化(Session707&709脱水版)
    • GPUImage源码解读(十一)
    • 一种避免 iOS 内存碎片的方法
    • pods的原理
    • GPUImage源码解读(十二)
    • GPUImage源码解读(十三)
    • iOS 11 Layout的新特性
    • iOS应用瘦身方法思路整理
    • GPUImage源码解读(十四)
    • CAEmitterLayer属性介绍
    • 浅析移动蜂窝网络的特点及其省电方案
    • 如何在 table view 中添加 3D Touch Peek & Pop 功能
    • iOS中锁的介绍与使用
    • NSLog效率低下的原因及尝试lldb断点打印Log
    • GPUImage源码解读(十五)
    • GPUImage源码解读(十六)
    • CADisplayLink
    • GPUImage源码解读(十七)
    • CADisplayLink
    • 老生常谈category增加属性的几种操作
    • 30行代码演示dispatch_once死锁
    • GPUImage源码解读(十八)
    • YYImage设计思路
    • GPUImage源码解读(十九)
    • 深入理解Tagged Pointer
    • iOS 11:WKWebView内容过滤规则详解
    • Swift语法对编译速度的影响
    • GPUImage源码解读(二十)
    • GPUImage源码解读(二十一)
    • iOS App间常用的五种通信方式
    • YYCache深入学习
    • 冲顶大会插件
    • iOS高性能图片架构与设计
    • YUV颜色编码解析
    • iOS传感器:App前后台切换后,获取敏感信息使用touch ID进行校验
    • GPUImage源码解读(二十二)
    • GPUImage源码解读(二十三)
    • 从零开始的机器学习 - Machine Learning(一)
    • 从零开始的机器学习 - Machine Learning(二)
    • GPUImage源码解读(二十四)
    • Objective-C消息转发机制
    • iOS 程序 main 函数之前发生了什么
    • MMKV--基于 mmap 的 iOS 高性能通用 key-value 组件
    • Objective-C 消息发送与转发机制原理
    • 谈Objective-C block的实现
    • GPUImage源码解读(二十五)
    • podfile语法
    • 轻量级低风险 iOS 热更新方案
    • 使用objection来模块化开发iOS项目
    • swift 中delegate的使用注意
    • 使用appledoc自动生成api文档
    • UITextChecker的使用
    • ARKit 如何给SCNNode贴Gif图片
    • Unity与iOS平台交互和原生插件开发
    • SceneKit编程珠玑
Powered by GitBook
On this page
  • 逻辑控制流
  • 一句话简单的介绍什么是并发:
  • 进程
  • 线程
  • 现在正式来到多线程的世界
  • 信号量
  • 没有中断的操作
  • 使用信号量来实现互斥
  • 并行程序的性能
  • 线程安全
  1. 每周阅读

ios一窥并发底层

PreviousCVPixelBufferCreate使用的坑NextARKit进阶:物理世界

Last updated 7 years ago

逻辑控制流

在我们系统中通常是会有其它程序在运行,进程是可以告诉每一个程序它是独自在使用处理器。这个时候如果有调试器单步去执行程序,就会出现一系列的程序计数器( PC ) 值,这些值唯一的对应于包含在程序的可执行目标文件的指令。这个所谓的 PC 值叫做 逻辑控制流

一句话简单的介绍什么是并发:

  • 如果逻辑控制流在时间上重叠就是并发 (concurrent)

    e.g:

往宏观上讲:在计算机系统中硬件异常处理程序, 我们进行 Command + C的时候 往微观上讲:I/O 多路复用,应用程序在一个进程的上下文中显示地调度它们的逻辑流。逻辑流被模型化为状态机,数据到达文件描述符后,主程序显式地从一个状态转换到另一个状态。 .... 如果在底层上面扣太多,就不是 iOS 方面的内容了。

我们知道对应用层的开发都是通过对底层的一个API 的封装,这里也会简单介绍一下底层方面的理论。

如果要了解并发编程就免不了进程,线程这些字眼。

进程

我相信从事 IT 行业的开发人员,对于它是不陌生的,千篇一律的话我就不多说了。下面就简单聊点不常知道的。

首先进程有独立的虚拟地址空间,如果想要和其他流进行通信,就是进程与进程之间进行通信,控制流必须使用某种显示的进程间通信机制 (IPC)

线程

线程是运行在一个单一进程上下文的逻辑流,由内核进行调度。

在 上我看到有用 pthread 进行示例证明。 我这里会适当的补充一点。

Posix 线程(Pthread) 是在 C 程序中处理线程的一个标准接口,在所有的 Linux 上都适用。 那么 Objective- C 对它的依赖就可想而知了。

Pthread 定义了大约 60多个 个函数,它们分别用来进行创建,杀死,和回收线程.对线程安全地共享数据,通知对等线程系统状态的变化等等。

直接适用 Pthread 的函数是非常繁琐的,那么到了 OC 这里就免不了对它进行了二次封装, 就这样来到了 Cocoa 那么到了 Swift 里面也基本是换汤不换药的拿来即用。

现在正式来到多线程的世界

先说点不厌其烦的废话知识: 锁

在 Objective-C 中常用的加锁方式有用 @synchronized 来修饰变量,以此来保证变量在作用范围内不会被其他线程改变。

那在 Swift 中是用 objc_sync_enter 与 objc_sync_exit 配合来使用加锁

上面提到锁相关的一些信息,那么它存在的目的无非就是一个:就是在多线程共享相同的程序变量

那么它在底层的原理是什么?正是这里要讲的。

问题 1.多线程存在的时候其基础的内存模型是什么? 2.变量如何映射到内存里面去? 3.引用这些变量的线程有多少?

每个线程都有它自己独立的线程上下文,包括线程的 ID, 栈 , 栈指针 ,程序计数器, 条件码, 通用目的寄存器

每个线程和其他线程一起共享进程的上下文的剩余部分。这包括整个用户虚拟地址空间,它是由只读文本,读/写数据, 栈以及所有的共享库代码和数据区域组成。

任何线程都可以访问共享虚拟内存的任意位置, 如果众多线程中的某一个线程修改了一个内存位置,那其他的线程都能在它读到这个位置时发现这个变化。

虚拟内存对相关变量的一些操作

1、全局变量: 虚拟内存的读/写区域只包含每个全局变量的一个实例,任何线程都可以调用

2、本地变量: 每个线程的栈都包含它自己的所有本地自动变量

3、本地的静态变量:本地带 Static 属性, 虚拟内存的读/写区域只包含程序中声明的每个本地静态变量的一个实例

信号量

  • 计数器 (引用计数)

  • 同步错误 synchronization error

  • 进度图 progress graph

计数器 (引用计数) 与 同步错误 (synchronization error)

在多线程访问同一个全局变量的时候,我们查看对计数相关的汇编代码,过程大致如下: 1、加载全局变量 cnt 到累加寄存器 %rdx (当前线程的寄存器 %rdx 的值) 2、增加 %rdx 的指令 3、将%rdx的更新值存回到共享变量 cnt 的指令

当然在iOS当中不会直接这样计数,因为这样会存在一个很大的问题:

当两个线程同时对一个计数器的值进行读取,并加1,再将结果写到内存中去,这个时候计数器就会出现问题。因为计数器加了两次而写到内存中确是相当于只加了一次的那个值

线程 A 和 B 都从内存中读取出了计数器的值,假设为 1 ,然后线程A将计数器的值加1,并将结果 2 写回到内存中。同时,线程B也将计数器的值加 1 ,并将结果 2 写回到内存中。实际上,此时计数器的值已经被破坏掉了,因为计数器的值 1 被加 1 了两次,而它的值却是 2。

进度图 progress graph

每条轴的 k 对应线程 k 的进度。每个点代表线程 k已经完成了指令 I_k的状态。

我们上面讲的:

在多线程访问同一个全局变量的时候,我们查看对计数相关的汇编代码,过程大致分为3步

我们这里设定在 A 线程的时候步骤为:

同理设定在 B 线程的时候步骤为:

这个时候我们来看下图

我们看到图中有一个点 (A1,B3)。

这个点的意思就是:当线程 A 完了第 A1 状态的同时,线程 B 完成了 B3 状态。

使用进度图的目的就是讲指令执行模型转化为从一种状态到另一种状态的转换。

这样就可以把程序的执行历史转换为状态空间中的一条轨迹线。

对于线程不管是 A 或者 B 也好,对全局变量的的操作(A1,A2,A3)步骤或者 (B1,B2,B3)步骤的过程中构成了一个临界区,这个临界区不应该和其他进程的临界区交替执行。我们确保每个线程在执行它的临界区中的指令时,拥有对共享变量 的 互斥 的访问( Mutually exclusive access). 通常这种现象称为互斥(Mutual exclusion).

这样在上图里面会出现这样的规则:相同指令不能再同一时刻完成,对角线的线是不存在的。

两个临界区的交集形成的状态空间区域称为不安全区(unsafe region)

安全轨迹线:不在不安全区的轨迹线 不安全轨迹线:雷区的轨迹线

任何安全轨迹线都将正确地更新共享计数器。为了保证任意的全局变量在并发线程的正确执行,我们就必须以某种方式同步线程,使他们总是有一条安全轨迹线。其思想原理的基本思想就是基于信号量

信号量: (semaphore) 一种特殊类型的变量。 信号量以s表示.是具有非负整数值的全局变量,只能有两种特殊的操作来处理,这两种操作称为 P 和 V:

P(s): 如果 s 是非零的,那么P 将 s 减1,并且立即返回。如果 S 为零,那么就挂起这个线程,直到 s 为零,而一个 V 操作会重启这个线程。在重启之后,P 操作将 s减1,并将控制返回给调用者。 V(s): V操作将 s 加1。如果有任何线程阻塞在 P 操作等待 s 变成非零,那么 V 操作会重启这些线程中的一个,然后该线程将 s 减1,完成它的 P 操作。 P 中的测试和减1操作是不可分割的,一旦预测信号量s 变为非零,就会将s减1,不能有中断操作,这个过程中不会有中断。 V 的加1 操作也是不可分割的。

没有中断的操作

PS:V 的定义中没有定义等待线程被重启动的顺序。唯一的要求是 V 必须只能重启一个正在等待的线程。因此,当有多个线程在等待同一个信号量时,就不能预测 V 操作要重启哪一个线程。

P和V 的定义确保了一个正在运行的程序绝不可能进入这一种状态,也就是一个正确初始化了的信号量有一个负值。这个属性称为 信号量不变性(semaphore invariant)

使用信号量来实现互斥

作用是:

将每个全局变量 与 一个信号量 s = 1 联系起来,然后用 P(s) 和 V(s) 操作将相应的临界区包围起来。这种方式成为 二元信号量 (binary semaphore),它的值要么是 0 要么是 1。以提供互斥为目的的二元型号量常常称为 互斥锁 (mutex).

那么在一个互斥锁上执行 P 操作称为对互斥锁加锁。执行 V操作称为对互斥锁解锁。对一个互斥锁加了锁但是还没有解锁的线程称为占用了这个互斥锁。 一个被用作一组可用资源的计数器的信号量被称为 计数信号量。

如上面的雷区图,在雷区内因为信号量的不确定性故: s < 0

以上看到的仍然是坑: 因为上面是单处理器的讲解

但是有一个是万用的:同步对共享变量的访问是必须的。

多线程中对相同资源的访问:

案例1: 在多媒体开发过程中对视频的帧编码,并实时播放。这个时候就会有一个缓存的东西存在,其存在的目的是为了减少视频流的抖动,引起的原因是帧的编码与解码时与数据相关的差异引起的。

案例2: 我们开发过程中对手机屏幕点击事件的产生后,该事件先进入缓存中,然后多线程根据优先级来从缓冲区里面取出该事件进行响应。这就能很好解释有时点击屏幕卡屏了一会儿才响应。

饥饿问题: 这个网上帖子泛滥: 传送门 Obj中国

多个线程并行处理分配给它们的区域处理方法:

主线程给其他开的线程一个整数理解为该线程的 ID。每个线程用它的ID来决定它应该计算序列的哪一部分。

并行程序的性能

运行时间是衡量程序性能的最终标准。相对衡量标准能够说明并行程序有多好地利用了潜在的并行性。

并行程序的加速比(speedup)通常定义为: Sp = T1/Tp

p 是处理器的核树,Tk 是在 K 个核上的运行时间。这个公式被称为:强扩展(strong scaling).

当T1是程序顺序执行版本的执行时间时,Sp称为 绝对加速比 (absolute speedup). 当T1是程序并行版本在一个核上的执行时间,Sp称为 相对加速比 (absolute speedup). 绝对加速比会比相对加速比更加难以测量,因为测量绝对加速比需要程序的两种不同的版本。对于复杂的并行代码,创造一个独立的顺序版本也不现实。

效率: Ep = Sp/p = T1/pTp

弱扩展:(weak scaling):在增加处理器数量的同时,增加问题的规模,这样随着处理器的数量的增加,每个处理器执行的工作量保存不变,在这样的情况下加速比和效率被表达为单位时间完成的工作量。

线程安全

首先被称为线程安全是当且仅当被多个多线程反复的调用,它才会一直产生正确的结果。如果一个函数设计的不是线程安全的,它就是线程不安全的。

线程不安全的函数定义:

1、对全局变量的保护 2、保存跨越多个调用的状态函数。如:随机数生产的函数 3、返回指向静态变量的指针的函数。 4、调用线程不安全函数的函数。

引用 上面一个例子:

在iOS 开发的应用层上面来看就是加锁等等一系列的操作。在真正核心底层方面好多文章是没有具体去讲的,您可以综合性的看看其他的文章,推荐 上面关于多线程系统的讲法。好了,我们继续

将 n 个并发线程的执行抽象为一条 n 维 中的轨迹线。

Obj中国
Obj中国
Obj中国
笛卡尔空间