# 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_enter 與 objc_sync_exit 配合來使用加鎖
上面提到鎖相關的一些信息,那么它存在的目的無非就是一個:就是在多線程共享相同的程序變量
那么它在底層的原理是什么?正是這里要講的。
| 1.多線程存在的時候其基礎的內存模型是什么? |
| 2.變量如何映射到內存里面去? |
| 3.引用這些變量的線程有多少? |
每個線程都有它自己獨立的線程上下文,包括線程的 ID, 棧 , 棧指針 ,程序計數器, 條件碼, 通用目的寄存器
每個線程和其他線程一起共享進程的上下文的剩余部分。這包括整個用戶虛擬地址空間,它是由只讀文本,讀/寫數據, 棧以及所有的共享庫代碼和數據區域組成。
任何線程都可以訪問共享虛擬內存的任意位置, 如果眾多線程中的某一個線程修改了一個內存位置,那其他的線程都能在它讀到這個位置時發現這個變化。
虛擬內存對相關變量的一些操作
全局變量:
虛擬內存的讀/寫區域只包含每個全局變量的一個實例,任何線程都可以調用
本地變量:
每個線程的棧都包含它自己的所有本地自動變量
本地的靜態變量:本地帶 Static 屬性, 虛擬內存的讀/寫區域只包含程序中聲明的每個本地靜態變量的一個實例
信號量
計數器 (引用計數)
同步錯誤 synchronization error
進度圖 progress graph
計數器 (引用計數) 與 同步錯誤 (synchronization error)
在多線程訪問同一個全局變量的時候,我們查看對計數相關的匯編代碼,過程大致如下:
當然在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 操作也是不可分割的。
沒有中斷的操作
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).
絕對加速比會比相對加速比更加難以測量,因為測量絕對加速比需要程序的兩種不同的版本。對于復雜的并行代碼,創造一個獨立的順序版本也不現實。
效率: Ep = Sp/p = T1/pTp
弱擴展:(weak scaling): 在增加處理器數量的同時,增加問題的規模,這樣隨著處理器的數量的增加,每個處理器執行的工作量保存不變,在這樣的情況下加速比和效率被表達為單位時間完成的工作量。
##線程安全
首先被稱為線程安全是當且僅當被多個多線程反復的調用,它才會一直產生正確的結果。如果一個函數設計的不是線程安全的,它就是線程不安全的。
線程不安全的函數定義:
每個的具體例子有點多分下一章節進行
iOS 一窺并發編程底層(二)
總結
以上是生活随笔為你收集整理的# iOS 一窥并发编程底层(一)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: IoT 云服务加速产业创新,推进规模商用
- 下一篇: 单选框radio绑定click事件