一、進程、線程
一、 進程:
1.進程是一個具有一定獨立功能的程序關于某次數據集合的一次運行活動,它是操作系統分配資 源的基本單元.
2.進程是指在系統中正在運行的一個應用程序,就是一段程序的執行過程,我們可以理解為手機上 的一個 app.
3.每個進程之間是獨立的,每個進程均運行在其專用且受保護的內存空間內,擁有獨立運行所需 的全部資源
二、 線程
1.程序執行流的最小單元,線程是進程中的一個實體.
-
2.一個進程要想執行任務,必須至少有一條線程.應用程序啟動的時候,系統會默認開啟一條線程,
也就是主線程
三、 進程和線程的關系
1.線程是進程的執行單元,進程的所有任務都在線程中執行
2.線程是 CPU 分配資源和調度的最小單位
3.一個程序可以對應多個進程(多進程),一個進程中可有多個線程,但至少要有一條線程
4.同一個進程內的線程共享進程資源
二、多進程、多線程
多進程 打開 mac 的活動監視器,可以看到很多個進程同時運行
進程是程序在計算機上的一次執行活動。當你運行一個程序,你就啟動了一個進程。顯然,程序 是死的(靜態的),進程是活的(動態的)。
進程可以分為系統進程和用戶進程。凡是用于完成操作系統的各種功能的進程就是系統進程,它 們就是處于運行狀態下的操作系統本身;所有由用戶啟動的進程都是用戶進程。進程是操作系統進 行資源分配的單位。
進程又被細化為線程,也就是一個進程下有多個能獨立運行的更小的單位。在同一個時間里,同 一個計算機系統中如果允許兩個或兩個以上的進程處于運行狀態,這便是多進程。
多線程
1. 同一時間,CPU只能處理1條線程,只有1條線程在執行。多線程并發執行,其實是CPU快速地在多條 線程之間調度(切換)。如果 CPU 調度線程的時間足夠快,就造成了多線程并發執行的假象
2. 如果線程非常非常多,CPU會在N多線程之間調度,消耗大量的CPU資源,每條線程被調度執行的頻次 會降低(線程的執行效率降低)
3. 多線程的優點: 能適當提高程序的執行效率 能適當提高資源利用率(CPU、內存利用率)
4. 多線程的缺點:
開啟線程需要占用一定的內存空間(默認情況下,主線程占用 1M,子線程占用 512KB),如果開啟大量的 線程,會占用大量的內存空間,降低程序的性能
線程越多,CPU 在調度線程上的開銷就越大 程序設計更加復雜:比如線程之間的通信、多線程的數據共享
三、任務、隊列
任務
就是執行操作的意思,也就是在線程中執行的那段代碼。在 GCD 中是放在 block 中的。執行任務有兩種 方式:同步執行(sync)和異步執行(async)
同步(Sync):同步添加任務到指定的隊列中,在添加的任務執行結束之前,會一直等待,直到隊列里面的 任務完成之后再繼續執行,即會阻塞線程。只能在當前線程中執行任務(是當前線程,不一定是主線程), 不具備開啟新線程的能力。
異步(Async):線程會立即返回,無需等待就會繼續執行下面的任務,不阻塞當前線程。可以在新的線程中 執行任務,具備開啟新線程的能力(并不一定開啟新線程)。如果不是添加到主隊列上,異步會在子線程中 執行任務
隊列
隊列(Dispatch Queue):這里的隊列指執行任務的等待隊列,即用來存放任務的隊列。隊列是一種特殊 的線性表,采用 FIFO(先進先出)的原則,即新任務總是被插入到隊列的末尾,而讀取任務的時候總是從 隊列的頭部開始讀取。每讀取一個任務,則從隊列中釋放一個任務
在 GCD 中有兩種隊列:串行隊列和并發隊列。兩者都符合 FIFO(先進先出)的原則。兩者的主要區別是: 執行順序不同,以及開啟線程數不同。
串行隊列(Serial Dispatch Queue): 同一時間內,隊列中只能執行一個任務,只有當前的任務執行完成之后,才能執行下一個任務。(只 開啟一個線程,一個任務執行完畢后,再執行下一個任務)。主隊列是主線程上的一個串行隊列,是 系統自動為我們創建的
-
并發隊列(Concurrent Dispatch Queue): 同時允許多個任務并發執行。(可以開啟多個線程,并且同時執行任務)。并發隊列的并發功能只有 在異步(dispatch_async)函數下才有效
四、iOS 中的多線程
主要有三種:NSThread、NSoperationQueue、GCD
1. NSThread:輕量級別的多線程技術
是我們自己手動開辟的子線程,如果使用的是初始化方式就需要我們自己啟動,如果使用的是構造器方式 它就會自動啟動。只要是我們手動開辟的線程,都需要我們自己管理該線程,不只是啟動,還有該線程使 用完畢后的資源回收
4516DA37-6CD3-4592-B140-718A768AD033.pngperformSelector...只要是 NSObject 的子類或者對象都可以通過調用方法進入子線程和主線程,其實這些 方法所開辟的子線程也是 NSThread 的另一種體現方式。 在編譯階段并不會去檢查方法是否有效存在,如果不存在只會給出警告
需要注意的是:如果是帶 afterDelay 的延時函數,會在內部創建一個 NSTimer,然后添加到當前線程的 Runloop 中。也就是如果當前線程沒有開啟 runloop,該方法會失效。在子線程中,需要啟動 runloop(注 意調用順序)
而 performSelector:withObject:只是一個單純的消息發送,和時間沒有一點關系。所以不需要添加到子 線程的 Runloop 中也能執行
2、GCD 對比 NSOprationQueue
我們要明確 NSOperationQueue 與 GCD 之間的關系
GCD 是面向底層的 C 語言的 API,NSOpertaionQueue 用 GCD 構建封裝的,是 GCD 的高級抽象。
1、GCD 執行效率更高,而且由于隊列中執行的是由 block 構成的任務,這是一個輕量級的數據結構,寫起 來更方便
2、GCD 只支持 FIFO 的隊列,而 NSOperationQueue 可以通過設置最大并發數,設置優先級,添加依賴關系 等調整執行順序
3、NSOperationQueue 甚至可以跨隊列設置依賴關系,但是 GCD 只能通過設置串行隊列,或者在隊列內添 加 barrier(dispatch_barrier_async)任務,才能控制執行順序,較為復雜
4、NSOperationQueue 因為面向對象,所以支持 KVO,可以監測 operation 是否正在執行(isExecuted)、 是否結束(isFinished)、是否取消(isCanceld)
實際項目開發中,很多時候只是會用到異步操作,不會有特別復雜的線程關系管理,所以蘋果推崇的 且優化完善、運行快速的 GCD 是首選
如果考慮異步操作之間的事務性,順序行,依賴關系,比如多線程并發下載,GCD需要自己寫更多的 代碼來實現,而 NSOperationQueue 已經內建了這些支持
不論是GCD還是NSOperationQueue,我們接觸的都是任務和隊列,都沒有直接接觸到線程,事實上 線程管理也的確不需要我們操心,系統對于線程的創建,調度管理和釋放都做得很好。而 NSThread 需要我們自己去管理線程的生命周期,還要考慮線程同步、加鎖問題,造成一些性能上的開銷
五、GCD---隊列 iOS 中,有 GCD、NSOperation、NSThread 等幾種多線程技術方案。
而 GCD 共有三種隊列類型: main queue:通過 dispatch_get_main_queue()獲得,這是一個與主線程相關的串行隊列。
global queue:全局隊列是并發隊列,由整個進程共享。存在著高、中、低三種優先級的全局隊列。調用 dispath_get_global_queue 并傳入優先級來訪問隊列。
自定義隊列:通過函數 dispatch_queue_create 創建的隊列
六、死鎖 死鎖就是隊列引起的循環等待
1、一個比較常見的死鎖例子:主隊列同步
在主線程中運用主隊列同步,也就是把任務放到了主線程的隊列中。 同步對于任務是立刻執行的,那么當把任務放進主隊列時,它就會立馬執行,只有執行完這個任務, viewDidLoad 才會繼續向下執行。
而 viewDidLoad 和任務都是在主隊列上的,由于隊列的先進先出原則,任務又需等待 viewDidLoad 執行完 畢后才能繼續執行,viewDidLoad 和這個任務就形成了相互循環等待,就造成了死鎖。 想避免這種死鎖,可以將同步改成異步 dispatch_async,或者將 dispatch_get_main_queue 換成其他串行 或并行隊列,都可以解決。
2、同樣,下邊的代碼也會造成死鎖:
外面的函數無論是同步還是異步都會造成死鎖。
這是因為里面的任務和外面的任務都在同一個 serialQueue 隊列內,又是同步,這就和上邊主隊列同步的 例子一樣造成了死鎖
解決方法也和上邊一樣,將里面的同步改成異步 dispatch_async,或者將 serialQueue 換成其他串行或并 行隊列,都可以解決
這樣是不會死鎖的,并且 serialQueue 和 serialQueue2 是在同一個線程中的。
七、GCD 任務執行順序
1、串行隊列先異步后同步
打印順序是 13245
原因是:
首先先打印 1
接下來將任務 2 其添加至串行隊列上,由于任務 2 是異步,不會阻塞線程,繼續向下執行,打印 3 然后是任務 4,將任務 4 添加至串行隊列上,因為任務 4 和任務 2 在同一串行隊列,根據隊列先進先出原則, 任務 4 必須等任務 2 執行后才能執行,又因為任務 4 是同步任務,會阻塞線程,只有執行完任務 4 才能繼 續向下執行打印 5
所以最終順序就是 13245。
這里的任務 4 在主線程中執行,而任務 2 在子線程中執行。
如果任務 4 是添加到另一個串行隊列或者并行隊列,則任務 2 和任務 4 無序執行(可以添加多個任務看效果)
2、performSelector
這里的 test 方法是不會去執行的,原因在于
這個方法要創建提交任務到 runloop 上的,而 gcd 底層創建的線程是默認沒有開啟對應 runloop 的,所有 這個方法就會失效。
而如果將 dispatch_get_global_queue 改成主隊列,由于主隊列所在的主線程是默認開啟了 runloop 的, 就會去執行(將 dispatch_async 改成同步,因為同步是在當前線程執行,那么如果當前線程是主線程,test 方法也是會去執行的)。
八、dispatch_barrier_async
1、問:怎么用 GCD 實現多讀單寫?
多讀單寫的意思就是:可以多個讀者同時讀取數據,而在讀的時候,不能取寫入數據。并且,在寫的過程 中,不能有其他寫者去寫。即讀者之間是并發的,寫者與讀者或其他寫者是互斥的。
這里的寫處理就是通過柵欄的形式去寫。 就可以用 dispatch_barrier_sync(柵欄函數)去實現
2、dispatch_barrier_sync 的用法:
這里的 dispatch_barrier_sync 上的隊列要和需要阻塞的任務在同一隊列上,否則是無效的。 從打印上看,任務 0-9 和任務任務 10-19 因為是異步并發的原因,彼此是無序的。而由于柵欄函數的存在, 導致順序必然是先執行任務 0-9,再執行柵欄函數,再去執行任務 10-19。
dispatch_barrier_sync: Submits a barrier block object for execution and waits until that block completes.(提交一個柵欄函數在執行中,它會等待柵欄函數執行完)
dispatch_barrier_async: Submits a barrier block for asynchronous execution and returns immediately.(提交一個柵欄函數在異步執行中,它會立馬返回)
而 dispatch_barrier_sync 和 dispatch_barrier_async 的區別也就在于會不會阻塞當前線程 比如,上述代碼如果在 dispatch_barrier_async 后隨便加一條打印,則會先去執行該打印,再去執 行任務 0-9 和柵欄函數;而如果是 dispatch_barrier_sync,則會在任務 0-9 和柵欄函數后去執行這 條打印。
3、則可以這樣設計多讀單寫:
[圖片上傳中...(C5F76CA6-44FA-4AB7-9FB6-FAA9BAF84BBD.png-b2f98-1590378545844-0)]
九、dispatch_group_async
場景:在 n 個耗時并發任務都完成后,再去執行接下來的任務。比如,在 n 個網絡請求完成后去刷新 UI 頁 面。
十、Dispatch Semaphore
GCD 中的信號量是指 Dispatch Semaphore,是持有計數的信號。 Dispatch Semaphore 提供了三個函數
1.dispatch_semaphore_create:創建一個 Semaphore 并初始化信號的總量
2.dispatch_semaphore_signal:發送一個信號,讓信號總量加 1 #####3.dispatch_semaphore_wait:可以使總信號量減 1,當信號總量為 0 時就會一直等待(阻塞所在線程),否 則就可以正常執行。
Dispatch Semaphore 在實際開發中主要用于:
保持線程同步,將異步執行任務轉換為同步執行任務
保證線程安全,為線程加鎖
1、保持線程同步:
dispatch_semaphore_wait 加鎖阻塞了當前線程,dispatch_semaphore_signal 解鎖后當前線程繼續執行
2、保證線程安全,為線程加鎖:
在線程安全中可以將 dispatch_semaphore_wait 看作加鎖,而 dispatch_semaphore_signal 看作解鎖 首先創建全局變量
注意到這里的初始化信號量是 1。
異步并發調用 asyncTask
然后發現打印是從任務 1 順序執行到 100,沒有發生兩個任務同時執行的情況。
原因如下: 在子線程中并發執行 asyncTask,那么第一個添加到并發隊列里的,會將信號量減 1,此時信號量等于 0, 可以執行接下來的任務。而并發隊列中其他任務,由于此時信號量不等于 0,必須等當前正在執行的任務 執行完畢后調用 dispatch_semaphore_signal 將信號量加 1,才可以繼續執行接下來的任務,以此類推,從而 達到線程加鎖的目的。
十一、延時函數(dispatch_after)
dispatch_after 能讓我們添加進隊列的任務延時執行,該函數并不是在指定時間后執行處理,而只是在指 定時間追加處理到 dispatch_queue
由于其內部使用的是 dispatch_time_t 管理時間,而不是 NSTimer。 所以如果在子線程中調用,相比 performSelector:afterDelay,不用關心 runloop 是否開啟
十二、使用 dispatch_once 實現單例
十三、NSOperationQueue 的優點
NSOperation、NSOperationQueue 是蘋果提供給我們的一套多線程解決方案。實際上 NSOperation、 NSOperationQueue 是基于 GCD 更高一層的封裝,完全面向對象。但是比 GCD 更簡單易用、代碼可讀性 也更高。
1、可以添加任務依賴,方便控制執行順序
2、可以設定操作執行的優先級
3、任務執行狀態控制:isReady,isExecuting,isFinished,isCancelled
如果只是重寫 NSOperation 的 main 方法,由底層控制變更任務執行及完成狀態,以及任務退出 如果重寫了 NSOperation 的 start 方法,自行控制任務狀態
系統通過 KVO 的方式移除 isFinished==YES 的 NSOperation
3、可以設置最大并發量 十四、NSOperation 和 NSOperationQueue ? 操作(Operation):
執行操作的意思,換句話說就是你在線程中執行的那段代碼。
在 GCD 中是放在 block 中的。在 NSOperation 中,使用 NSOperation 子類 NSInvocationOperation、 NSBlockOperation,或者自定義子類來封裝操作。
操作隊列(Operation Queues):
這里的隊列指操作隊列,即用來存放操作的隊列。不同于 GCD 中的調度隊列 FIFO(先進先出)的原則。 NSOperationQueue 對于添加到隊列中的操作,首先進入準備就緒的狀態(就緒狀態取決于操作之間的依賴 關系),然后進入就緒狀態的操作的開始執行順序(非結束執行順序)由操作之間相對的優先級決定(優 先級是操作對象自身的屬性)。
操作隊列通過設置最大并發操作數(maxConcurrentOperationCount)來控制并發、串行。 NSOperationQueue 為我們提供了兩種不同類型的隊列:主隊列和自定義隊列。主隊列運行在主線程之上,
而自定義隊列在后臺執行。
十五、NSThread+runloop 實現常駐線程 NSThread 在實際開發中比較常用到的場景就是去實現常駐線程。
由于每次開辟子線程都會消耗cpu,在需要頻繁使用子線程的情況下,頻繁開辟子線程會消耗大量的 cpu,而且創建線程都是任務執行完成之后也就釋放了,不能再次利用,那么如何創建一個線程可以 讓它可以再次工作呢?也就是創建一個常駐線程。
首先常駐線程既然是常駐,那么我們可以用 GCD 實現一個單例來保存 NSThread
這樣創建的 thread 就不會銷毀了嗎?
并沒有打印,說明 test 方法沒有被調用。 那么可以用 runloop 來讓線程常駐
這時候再去調用 performSelector 就有打印了。
十六、自旋鎖與互斥鎖 自旋鎖:
是一種用于保護多線程共享資源的鎖,與一般互斥鎖(mutex)不同之處在于當自旋鎖嘗試獲取鎖時以忙等 待(busy waiting)的形式不斷地循環檢查鎖是否可用。當上一個線程的任務沒有執行完畢的時候(被鎖住), 那么下一個線程會一直等待(不會睡眠),當上一個線程的任務執行完畢,下一個線程會立即執行。
在多 CPU 的環境中,對持有鎖較短的程序來說,使用自旋鎖代替一般的互斥鎖往往能夠提高程序的性能。
互斥鎖:
當上一個線程的任務沒有執行完畢的時候(被鎖住),那么下一個線程會進入睡眠狀態等待任務執行完畢, 當上一個線程的任務執行完畢,下一個線程會自動喚醒然后執行任務。
總結:
自旋鎖會忙等: 所謂忙等,即在訪問被鎖資源時,調用者線程不會休眠,而是不停循環在那里,直到被鎖 資源釋放鎖。
互斥鎖會休眠: 所謂休眠,即在訪問被鎖資源時,調用者線程會休眠,此時 cpu 可以調度其他線程工 作。直到被鎖資源釋放鎖。此時會喚醒休眠線程。
優缺點:
自旋鎖的優點在于,因為自旋鎖不會引起調用者睡眠,所以不會進行線程調度,CPU 時間片輪轉等耗時操 作。所有如果能在很短的時間內獲得鎖,自旋鎖的效率遠高于互斥鎖。
缺點在于,自旋鎖一直占用 CPU,他在未獲得鎖的情況下,一直運行--自旋,所以占用著 CPU,如果不 能在很短的時 間內獲得鎖,這無疑會使 CPU 效率降低。自旋鎖不能實現遞歸調用。
自旋鎖:atomic、OSSpinLock、dispatch_semaphore_t 互斥鎖:pthread_mutex、@ synchronized、NSLock、NSConditionLock 、NSCondition、NSRecursiveLock