# iOS 一窥并发编程底层(一)
語歌博客
邏輯控制流
在我們系統(tǒng)中通常是會有其它程序在運(yùn)行,進(jìn)程是可以告訴每一個(gè)程序它是獨(dú)自在使用處理器。這個(gè)時(shí)候如果有調(diào)試器單步去執(zhí)行程序,就會出現(xiàn)一系列的程序計(jì)數(shù)器( PC ) 值,這些值唯一的對應(yīng)于包含在程序的可執(zhí)行目標(biāo)文件的指令。這個(gè)所謂的 PC 值叫做 邏輯控制流
一句話簡單的介紹什么是并發(fā):
- 如果邏輯控制流在時(shí)間上重疊就是并發(fā) (concurrent)
e.g:
- 往宏觀上講:在計(jì)算機(jī)系統(tǒng)中硬件異常處理程序, 我們進(jìn)行 Command + C的時(shí)候
- 往微觀上講:I/O 多路復(fù)用,應(yīng)用程序在一個(gè)進(jìn)程的上下文中顯示地調(diào)度它們的邏輯流。邏輯流被模型化為狀態(tài)機(jī),數(shù)據(jù)到達(dá)文件描述符后,主程序顯式地從一個(gè)狀態(tài)轉(zhuǎn)換到另一個(gè)狀態(tài)。
- .... 如果在底層上面扣太多,就不是 iOS 方面的內(nèi)容了。
我們知道對 應(yīng)用層的開發(fā) 都是通過對底層的一個(gè) API 的封裝,這里也會簡單介紹一下底層方面的理論。
如果要了解并發(fā)編程就免不了 進(jìn)程,線程 這些字眼。
進(jìn)程
我相信從事 IT 行業(yè)的開發(fā)人員,對于它是不陌生的,千篇一律的話我就不多說了。下面就簡單聊點(diǎn)不常知道的。
首先進(jìn)程有獨(dú)立的虛擬地址空間,如果想要和其他流進(jìn)行通信,就是進(jìn)程與進(jìn)程之間進(jìn)行通信,控制流必須使用某種顯示的進(jìn)程間通信機(jī)制 (IPC)
線程
線程是運(yùn)行在一個(gè)單一進(jìn)程上下文的邏輯流,由內(nèi)核進(jìn)行調(diào)度。
在 Obj中國 上我看到有用 pthread 進(jìn)行示例證明。 我這里會適當(dāng)?shù)难a(bǔ)充一點(diǎn)。
Posix 線程(Pthread) 是在 C 程序中處理線程的一個(gè)標(biāo)準(zhǔn)接口,在所有的 Linux 上都適用。 那么 Objective- C 對它的依賴就可想而知了。
Pthread 定義了大約 60多個(gè) 個(gè)函數(shù),它們分別用來進(jìn)行創(chuàng)建,殺死,和回收線程.對線程安全地共享數(shù)據(jù),通知對等線程系統(tǒng)狀態(tài)的變化等等。
直接適用 Pthread 的函數(shù)是非常繁瑣的,那么到了 OC 這里就免不了對它進(jìn)行了二次封裝, 就這樣來到了 Cocoa 那么到了 Swift 里面也基本是換湯不換藥的拿來即用。
現(xiàn)在正式來到多線程的世界
先說點(diǎn)不厭其煩的廢話知識: 鎖
在 Objective-C 中常用的加鎖方式有用 @synchronized 來修飾變量,以此來保證變量在作用范圍內(nèi)不會被其他線程改變。
那在 Swift 中是用 objc_sync_enter 與 objc_sync_exit 配合來使用加鎖
上面提到鎖相關(guān)的一些信息,那么它存在的目的無非就是一個(gè):就是在多線程共享相同的程序變量
那么它在底層的原理是什么?正是這里要講的。
| 1.多線程存在的時(shí)候其基礎(chǔ)的內(nèi)存模型是什么? |
| 2.變量如何映射到內(nèi)存里面去? |
| 3.引用這些變量的線程有多少? |
每個(gè)線程都有它自己獨(dú)立的線程上下文,包括線程的 ID, 棧 , 棧指針 ,程序計(jì)數(shù)器, 條件碼, 通用目的寄存器
每個(gè)線程和其他線程一起共享進(jìn)程的上下文的剩余部分。這包括整個(gè)用戶虛擬地址空間,它是由只讀文本,讀/寫數(shù)據(jù), 棧以及所有的共享庫代碼和數(shù)據(jù)區(qū)域組成。
任何線程都可以訪問共享虛擬內(nèi)存的任意位置, 如果眾多線程中的某一個(gè)線程修改了一個(gè)內(nèi)存位置,那其他的線程都能在它讀到這個(gè)位置時(shí)發(fā)現(xiàn)這個(gè)變化。
虛擬內(nèi)存對相關(guān)變量的一些操作
全局變量:
虛擬內(nèi)存的讀/寫區(qū)域只包含每個(gè)全局變量的一個(gè)實(shí)例,任何線程都可以調(diào)用
本地變量:
每個(gè)線程的棧都包含它自己的所有本地自動變量
本地的靜態(tài)變量:本地帶 Static 屬性, 虛擬內(nèi)存的讀/寫區(qū)域只包含程序中聲明的每個(gè)本地靜態(tài)變量的一個(gè)實(shí)例
信號量
計(jì)數(shù)器 (引用計(jì)數(shù))
同步錯(cuò)誤 synchronization error
進(jìn)度圖 progress graph
計(jì)數(shù)器 (引用計(jì)數(shù)) 與 同步錯(cuò)誤 (synchronization error)
在多線程訪問同一個(gè)全局變量的時(shí)候,我們查看對計(jì)數(shù)相關(guān)的匯編代碼,過程大致如下:
當(dāng)然在iOS當(dāng)中不會直接這樣計(jì)數(shù),因?yàn)檫@樣會存在一個(gè)很大的問題:
當(dāng)兩個(gè)線程同時(shí)對一個(gè)計(jì)數(shù)器的值進(jìn)行讀取,并加1,再將結(jié)果寫到內(nèi)存中去,這個(gè)時(shí)候計(jì)數(shù)器就會出現(xiàn)問題。因?yàn)橛?jì)數(shù)器加了兩次而寫到內(nèi)存中確是相當(dāng)于只加了一次的那個(gè)值
引用 Obj中國 上面一個(gè)例子:
線程 A 和 B 都從內(nèi)存中讀取出了計(jì)數(shù)器的值,假設(shè)為 1 ,然后線程A將計(jì)數(shù)器的值加1,并將結(jié)果 2 寫回到內(nèi)存中。同時(shí),線程B也將計(jì)數(shù)器的值加 1 ,并將結(jié)果 2 寫回到內(nèi)存中。實(shí)際上,此時(shí)計(jì)數(shù)器的值已經(jīng)被破壞掉了,因?yàn)橛?jì)數(shù)器的值 1 被加 1 了兩次,而它的值卻是 2。
在iOS 開發(fā)的應(yīng)用層上面來看就是加鎖等等一系列的操作。在真正核心底層方面好多文章是沒有具體去講的,您可以綜合性的看看其他的文章,推薦 Obj中國 上面關(guān)于多線程系統(tǒng)的講法。好了,我們繼續(xù):
進(jìn)度圖 progress graph
將 n 個(gè)并發(fā)線程的執(zhí)行抽象為一條 n 維 笛卡爾空間 中的軌跡線.
每條軸的 k 對應(yīng)線程 k 的進(jìn)度。每個(gè)點(diǎn)代表線程 k已經(jīng)完成了指令 I_k的狀態(tài)。
我們上面講的:
在多線程訪問同一個(gè)全局變量的時(shí)候,我們查看對計(jì)數(shù)相關(guān)的匯編代碼,過程大致分為3步復(fù)制代碼我們這里設(shè)定在 A 線程的時(shí)候步驟為:
| A1 | A2 | A3 |
同理設(shè)定在 B 線程的時(shí)候步驟為:
| B1 | B2 | B3 |
這個(gè)時(shí)候我們來看下圖
我們看到圖中有一個(gè)點(diǎn) (A1,B3).
這個(gè)點(diǎn)的意思就是:當(dāng)線程 A 完了第 A1 狀態(tài)的同時(shí),線程 B 完成了 B3 狀態(tài)。
使用進(jìn)度圖的目的就是講指令執(zhí)行模型轉(zhuǎn)化為從一種狀態(tài)到另一種狀態(tài)的轉(zhuǎn)換。
這樣就可以把程序的執(zhí)行歷史轉(zhuǎn)換為狀態(tài)空間中的一條軌跡線。
對于線程不管是 A 或者 B 也好,對全局變量的的操作(A1,A2,A3)步驟或者 (B1,B2,B3)步驟的過程中構(gòu)成了一個(gè)臨界區(qū),這個(gè)臨界區(qū)不應(yīng)該和其他進(jìn)程的臨界區(qū)交替執(zhí)行。我們確保每個(gè)線程在執(zhí)行它的臨界區(qū)中的指令時(shí),擁有對共享變量 的 互斥 的訪問( Mutually exclusive access). 通常這種現(xiàn)象稱為互斥(Mutual exclusion).
這樣在上圖里面會出現(xiàn)這樣的規(guī)則:相同指令不能再同一時(shí)刻完成,對角線的線是不存在的。
兩個(gè)臨界區(qū)的交集形成的狀態(tài)空間區(qū)域稱為不安全區(qū)(unsafe region)
安全軌跡線:不在不安全區(qū)的軌跡線
不安全軌跡線:雷區(qū)的軌跡線
任何安全軌跡線都將正確地更新共享計(jì)數(shù)器。為了保證任意的全局變量在并發(fā)線程的正確執(zhí)行,我們就必須以某種方式同步線程,使他們總是有一條安全軌跡線。其思想原理的基本思想就是基于 信號量
信號量: (semaphore) 一種特殊類型的變量。
信號量以s表示.是具有非負(fù)整數(shù)值的全局變量,只能有兩種特殊的操作來處理,這兩種操作稱為 P 和 V:
P(s): 如果 s 是非零的,那么P 將 s 減1,并且立即返回。如果 S 為零,那么就掛起這個(gè)線程,直到 s 為零,而一個(gè) V 操作會重啟這個(gè)線程。在重啟之后,P 操作將 s減1,并將控制返回給調(diào)用者。
V(s): V操作將 s 加1。如果有任何線程阻塞在 P 操作等待 s 變成非零,那么 V 操作會重啟這些線程中的一個(gè),然后該線程將 s 減1,完成它的 P 操作。
P 中的測試和減1操作是不可分割的,一旦預(yù)測信號量s 變?yōu)榉橇?#xff0c;就會將s減1,不能有中斷操作,這個(gè)過程中不會有中斷。 V 的加1 操作也是不可分割的。
沒有中斷的操作
ps: V 的定義中沒有定義等待線程被重啟動的順序。唯一的要求是 V 必須只能重啟一個(gè)正在等待的線程。因此,當(dāng)有多個(gè)線程在等待同一個(gè)信號量時(shí),就不能預(yù)測 V 操作要重啟哪一個(gè)線程。
P和V 的定義確保了一個(gè)正在運(yùn)行的程序絕不可能進(jìn)入這一種狀態(tài),也就是一個(gè)正確初始化了的信號量有一個(gè)負(fù)值。這個(gè)屬性稱為 信號量不變性(semaphore invariant)
使用信號量來實(shí)現(xiàn)互斥
作用是:
將每個(gè)全局變量 與 一個(gè)信號量 s = 1 聯(lián)系起來,然后用 P(s) 和 V(s) 操作將相應(yīng)的臨界區(qū)包圍起來。這種方式成為 二元信號量 (binary semaphore),它的值要么是 0 要么是 1。以提供互斥為目的的二元型號量常常稱為 互斥鎖 (mutex).
那么在一個(gè)互斥鎖上執(zhí)行 P 操作稱為對互斥鎖加鎖。執(zhí)行 V操作稱為對互斥鎖解鎖。對一個(gè)互斥鎖加了鎖但是還沒有解鎖的線程稱為占用了這個(gè)互斥鎖。 一個(gè)被用作一組可用資源的計(jì)數(shù)器的信號量被稱為 計(jì)數(shù)信號量。
如上面的雷區(qū)圖,在雷區(qū)內(nèi)因?yàn)樾盘柫康牟淮_定性故: s < 0
以上看到的仍然是坑: 因?yàn)樯厦媸菃翁幚砥鞯闹v解
但是有一個(gè)是萬用的:同步對共享變量的訪問是必須的。
多線程中對相同資源的訪問:
案例1:
在多媒體開發(fā)過程中對視頻的幀編碼,并實(shí)時(shí)播放。這個(gè)時(shí)候就會有一個(gè)緩存的東西存在,其存在的目的是為了減少視頻流的抖動,引起的原因是幀的編碼與解碼時(shí)與數(shù)據(jù)相關(guān)的差異引起的。
案例2:
我們開發(fā)過程中對手機(jī)屏幕點(diǎn)擊事件的產(chǎn)生后,該事件先進(jìn)入緩存中,然后多線程根據(jù)優(yōu)先級來從緩沖區(qū)里面取出該事件進(jìn)行響應(yīng)。這就能很好解釋有時(shí)點(diǎn)擊屏幕卡屏了一會兒才響應(yīng)。
饑餓問題:
這個(gè)網(wǎng)上帖子泛濫: 傳送門 Obj中國
多個(gè)線程并行處理分配給它們的區(qū)域處理方法:
主線程給其他開的線程一個(gè)整數(shù)理解為該線程的 ID。每個(gè)線程用它的ID來決定它應(yīng)該計(jì)算序列的哪一部分。
##并行程序的性能
運(yùn)行時(shí)間是衡量程序性能的最終標(biāo)準(zhǔn)。相對衡量標(biāo)準(zhǔn)能夠說明并行程序有多好地利用了潛在的并行性。
并行程序的加速比(speedup)通常定義為: Sp = T1/Tp
p 是處理器的核樹,Tk 是在 K 個(gè)核上的運(yùn)行時(shí)間。這個(gè)公式被稱為:強(qiáng)擴(kuò)展(strong scaling).
絕對加速比會比相對加速比更加難以測量,因?yàn)闇y量絕對加速比需要程序的兩種不同的版本。對于復(fù)雜的并行代碼,創(chuàng)造一個(gè)獨(dú)立的順序版本也不現(xiàn)實(shí)。
效率: Ep = Sp/p = T1/pTp
弱擴(kuò)展:(weak scaling): 在增加處理器數(shù)量的同時(shí),增加問題的規(guī)模,這樣隨著處理器的數(shù)量的增加,每個(gè)處理器執(zhí)行的工作量保存不變,在這樣的情況下加速比和效率被表達(dá)為單位時(shí)間完成的工作量。
##線程安全
首先被稱為線程安全是當(dāng)且僅當(dāng)被多個(gè)多線程反復(fù)的調(diào)用,它才會一直產(chǎn)生正確的結(jié)果。如果一個(gè)函數(shù)設(shè)計(jì)的不是線程安全的,它就是線程不安全的。
線程不安全的函數(shù)定義:
每個(gè)的具體例子有點(diǎn)多分下一章節(jié)進(jìn)行
iOS 一窺并發(fā)編程底層(二)
總結(jié)
以上是生活随笔為你收集整理的# iOS 一窥并发编程底层(一)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: IoT 云服务加速产业创新,推进规模商用
- 下一篇: 单选框radio绑定click事件