并發 && 并行
多線程的同時執行并非"并行", 而是"并發", "并發"無論從宏觀還是微觀上都是同時執行的, 而"并行"宏觀上是同時執行, 微觀上仍然是一個cpu的在多條線程上的來回切換.
多線程解決了什么問題?
從第一個問題可以看出, cpu(單核)在多條線程之間輪詢執行的效率必然要低于單線程執行. 在單核cpu的機器上單線程的效率是最高的. 所以多線程的出現并非是為了解決cpu的效率問題 (注意: 并非說多線程不能提高cpu的效率),而是為了解決"阻塞"問題, 如: task1 在 thread1 執行需要10秒, task2 在 thread2 需要1秒, 人們不愿意等到 task1 執行完成之后才能看到 task2 的執行結果, 也就是不愿意 task1 阻塞 task2 的執行. 可以說多線程是為了解決"阻塞"問題而生的.(多核cpu的問題涉及到硬件, 還不是很清楚, 不誤導大家了)
線程的串行
線程是進程的執行單元/路徑
一個線程中執行多個任務, 是按順序一個個執行的
多線程的原理
CPU在各個線程中快速切換, 其調度線程的時間夠快, 就產生了多線程的"并發"執行的假象
多線程優缺點
優點:
- 可以使任何需要及時響應的任務及時響應, 如用戶UI可以在進行其它工作的同時一直處于活動狀態.
- 可以設置線程優先級.
- 可以適當提高資源利用率, 如: 下載速度最大總共1m/s的網絡環境下, 單線程下載 task1 && task2, 如果因為某些原因導致下載task1的速度只有100k/s, 那么下載完這2個任務的時間必然很長; 如果開啟2條線程下載, 下載速度就可能達到1m/s, 如此大大提高了帶寬的利用率.
缺點:
- 如果線程多每條線程被調度執行的頻次會降低(線程的執行效率低).
- 線程占用內存, 主線程默認占1M, 子線程占用512k.
- 線程間通信和線程間數據共享, 資源搶奪造成程序的復雜性.
線程的狀態
- 可調度線程池概念
系統底層用于管理線程的一個"池子", 裝著所有可供系統調度的線程. - 線程的狀態
- new
為線程分配內存空間 - runnable
線程進入可調度線程池 - running
線程有任務正在執行 - blocked
線程阻塞, 被移除可調度線程池 - dead
線程死亡, 系統回收內存
- new
線程安全問題
資源搶占
舉例: 如果一份文件需要張三和李四簽字, 可認為2人對應兩條線程, 若二人同時執行簽字, 可能最后簽字結果是:張四李三, 李張三四等.
若要解決此問題, 必須保證文件在同一段時間被一個人占用, 另一個人要么先"睡會", 要么"干點別的", "睡會"和"干點別的"對應兩種線程鎖.-
鎖
- 同步鎖: @synchronized
用上面的例子, 張三在簽字的過程中, 把文件''鎖住'', 李四準備來簽, 看到文件被鎖, 會"睡等" - 自旋鎖 OSSpinLock
用上面的例子, 張三在簽字的過程中, 把文件''鎖住'', 李四準備來簽, 看到文件被鎖, 會"轉圈等".
- 同步鎖: @synchronized
兩種鎖的效率:
?對于同步鎖,如果資源已經被占用,下一個調用者只能先進入睡眠狀態等待。但是自旋鎖不會引起調用者睡眠,如果自旋鎖已經被別的執行單元保持,下一個調用者就一直循環在那里看是否該自旋鎖的保持者已經釋放了鎖,"自旋"一詞就是因此而得名.
看上去 "自旋"會消耗大量資源, 效率更低. 實際情況是"睡"需要改變線程的狀態, 會把線程從可調度線程池中取出, 從 runnable 狀態到blocked 狀態. 而"自旋"在做一個空循環, 不分配任何內存空間, 相比與改變線程的狀態系統消耗的資源會少一些.
- 原子屬性
atomic :消耗較多資源, 相對線程安全
nonatomic:非線程安全
線程間通信
在一個進程中, 線程往往不是孤立存在的, 多線程經常需要相互通信:
- 一個線程傳遞數據給另一個線程
- 在一個線程中執行完特定的任務后, 轉到另一個線程繼續執行
iOS中多線程的實現方案
技術 | 簡介 | 語言 | 生命周期 |
---|---|---|---|
pthread | 跨平臺Unix Linux Windows | C | 需要管理 |
NSThread | 面向對象 可直接操作線程對象 | OC | 需要管理 |
GCD | 中樞調度系統(在隊列中調度任務到相應線程) | C | 系統管理 |
NSOperation | 對GCD的封裝, 面向對象 | OC | 系統管理 |
- pthread
基本過時, 很少能看見iOS項目中有使用
NSThread
iOS中 NSThread 多用于的判斷線程編號, 是否處于主線程等-
GCD
- 自動管理線程的生命周期 (創建線程, 調度任務), C,JAVA中需要關注
- 核心: 任務添加隊列, 任務的取出遵循隊列FIFO原則
在 GCD 中,一個同步函數只在完成了它預定的任務后才返回。
一個異步函數,剛好相反,會立即返回,預定的任務會完成但不會等它完成.因此,一個異步函數 . 不會阻塞當前線程去執行下一個函數.
-
NSOperation
- 對GCD的封裝, 是GCD的進一步抽象, NSOperation封裝了需要執行的操作和執行操作所需的數據,讓程序員面向對象開發.
- NSOperation本身是個抽象類,必須用其子類(系統提供子類, 也可以自定義子類)
- NSOperation 封裝了一些方法, 方便設置依賴, 方便取消操作, 方便判斷當前操作的狀態等.
- 建議較為底層的, 公用的多線程模塊使用NSOperation, 其效率雖比GCD略低, 不過其面向對象以及能實現繼承的優點就足以讓程序員們去使用.