# iOS 一窥并发编程底层(一)

语歌博客

逻辑控制流

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

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

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

e.g:

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

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

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


进程

我相信从事 IT 行业的开发人员,对于它是不陌生的,千篇一律的话我就不多说了。下面就简单聊点不常知道的。
首先进程有独立的虚拟地址空间,如果想要和其他流进行通信,就是进程与进程之间进行通信,控制流必须使用某种显示的进程间通信机制 (IPC)

线程

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

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

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

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

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


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

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

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

那在 Swift 中是用 objc_sync_enterobjc_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,再将结果写到内存中去,这个时候计数器就会出现问题。因为计数器加了两次而写到内存中确是相当于只加了一次的那个值

引用 Obj中国 上面一个例子:

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

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

进度图 progress graph

将 n 个并发线程的执行抽象为一条 n 维 笛卡尔空间 中的轨迹线.

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

我们上面讲的:

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

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

第一步 第二步 第三步
A1 A2 A3

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

第一步 第二步 第三步
B1 B2 B3

这个时候我们来看下图

我们看到图中有一个点 (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 操作也是不可分割的。

没有中断的操作

加载 加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).

  1. 当T1是程序顺序执行版本的执行时间时,Sp称为 绝对加速比 (absolute speedup).
  2. 当T1是程序并行版本在一个核上的执行时间,Sp称为 相对加速比 (absolute speedup).

绝对加速比会比相对加速比更加难以测量,因为测量绝对加速比需要程序的两种不同的版本。对于复杂的并行代码,创造一个独立的顺序版本也不现实。

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

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

线程安全

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

线程不安全的函数定义:

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

每个的具体例子有点多分下一章节进行

iOS 一窥并发编程底层(二)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,802评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,109评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,683评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,458评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,452评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,505评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,901评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,550评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,763评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,556评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,629评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,330评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,898评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,897评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,140评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,807评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,339评论 2 342

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,579评论 18 139
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,169评论 11 349
  • 引用自多线程编程指南应用程序里面多个线程的存在引发了多个执行线程安全访问资源的潜在问题。两个线程同时修改同一资源有...
    Mitchell阅读 1,973评论 1 7
  • 前言: 文韬互联早前注册了百家号,一直没有时间发布,最近一段时间开始捡起这个事情,将在百家号上发布原创文章,专注互...
    林文韬同学阅读 187评论 0 0
  • 伍家的七星伴月被挖后,男人们都回来了,厄运才刚刚启幕,为商的伍一郎,潮州经商被骗,多年积累的财富片刻消失殆尽...
    土壮阅读 432评论 0 0