iOS知識梳理 - 多線程(1)雜談

進程和線程

首先,一般所說的進程和線程都是系統內核層面的概念。html

進程(Process),直觀來說就是運行中的程序,它是系統進行資源分配的最小單位。即,系統給每一個進程分配虛擬地址空間等資源,進程間默認是不共享內存等資源的。python

線程(thread)自己理解起來其實更簡單,它是個依附於進程的更細粒度的調用單位,它有本身的棧等數據,但一般內存是共享的(在一個進程的多個線程間)。然而實際討論起來會更復雜一些。git

線程分爲內核級線程用戶級線程兩種,分類的標準主要是線程的調度者在覈內仍是核外。說白了,內核級線程就是系統提供的線程。而用戶級線程就是程序自身實現的線程。理論上他們的本質區別只是這些,但實際上確是天差地別。github

線程的調度策略大致上分兩種:搶佔式調度和協同式調度。搶佔式指線程的切換時機由系統控制,每一個線程都會得到或多或少的CPU時間片,單個線程卡死並不會卡住其它線程;協同式調度是指某一線程執行完或執行到必定階段後,主動讓出CPU,切換到另外一個線程執行,一個線程卡死了,別的線程也就得不到CPU資源,整個進程就全完了。golang

咱們通常討論的線程,都默認了調度方式爲搶佔式調度。而討論協同式調度的線程時,一般使用一個更高大上一點的詞:協程web

從另外一個維度來看一下,咱們使用的線程API,和內核級線程的對應關係,一般分爲一對1、多對一和多對多。一對一時其實就是內核級線程;多對一時是用戶級線程,實際實現時都是協同式調度的,即協程。而多對多時,即多個上層線程會被分派到多個內核級線程執行,這種被成爲混合型線程。編程

咱們通常討論的線程都是這種一對一模型下的線程,也是目前最普遍的線程實現。不管是Android仍是iOS,以及Java服務端,默認地,都是這種。咱們後面的討論也主要在這種模型下展開。swift

多對一模型的用戶級線程已經幾乎看不到了,這裏再也不討論。數組

多對多模型仍是很多的,這兩年很火的golang就是用的多對多模型,另外有一些jvm也有實現但默認選項還是一對一。以golang的goroutine爲例,實際實現能夠理解爲線程池+協程的結合。goroutine其實比較接近線程池中的task概念,排隊進入線程池執行。而它優於task的地方在於,goroutine是帶上下文的,即執行到一半時,能夠停下來帶着上下文從新排隊,排到它時繼續執行,這又是協程的特性了。這種底層設計結合優秀的上層語法,獲得的golang是個很是有吸引力的語言。網絡

參考:

線程和進程的區別是什麼?

內核態是指一個特殊的進程,仍是指進程的一種特殊狀態?

用戶級線程和內核級線程的區別

Linux下調用pthread庫建立的線程是屬於用戶級線程仍是內核級線程

線程究竟是什麼?

發散地探討了一些,下面仍是重點討論內核級線程。

線程池

多線程編程給咱們帶來了諸多便利,但仍有一些不方便的地方。好比出於性能考慮,線程數量不能太多。

並行是指物理層面上的同時處理,即CPU多個核心各自處理不一樣的事情。這很nice。

併發是指邏輯上的同時處理多個事情,實際上CPU多是在同一個核上分了時間片處理的,也就是前面說的搶佔式調度。線程數量越多,線程間切換的開銷也就越大。這就致使咱們不能隨意建立線程。

另外,若是有比較多的建立、銷燬線程操做,開銷也是比較大的。

爲了解決這一問題,最多見的方案是線程池模式。一開始就設定好池子裏線程的數量,把要執行的任務往池子裏丟,有線程空閒就處理,沒有就排隊。

GCD就是iOS平臺下基於線程池的方案,它暴露Queue、Task這樣的接口,一般只會使用CPU核心數那麼多的線程數量(參考Number of threads created by GCD?),保證了性能。相比直接調用線程接口,下降了心智負擔,而且有效地提升了多線程程序的下限。

線程衝突

線程衝突是多線程編程中最大的問題,容易遇到,又比較難搞。好比兩個線程都想要操做一個數組,一個想往裏塞數據,一個想要刪數據,就很容易衝突。沒作異常捕獲的話程序就直接掛了。

常規方案就是加鎖。可是加鎖是個,咋說呢,頗有學問的事情。加得很差會特別影響性能(→_→ 參考python的GIL)。一些複雜的業務場景下,鎖的問題可能會很是很是很是很是複雜。

這個問題的本質在於,線程間是經過共享內存來通訊的,同時讀寫同一塊內存時必然遇到衝突問題。因而,經過事件驅動/消息機制進行線程間通訊的方式逐漸受到人們關注。直白點來講,你須要這塊數據的時候我複製一份發給你,你就從消息通道讀數據,不要跟我共享別的變量之類的了。

通常場景下,這種方式帶來的內存和CPU開銷並不會太多,邏輯上卻比鎖要簡單太多了,所以這些年受到了普遍的關注。

如下幾個都是這種思想的實現:

有的語言(go)是把這做爲可選方案,而有的語言(dart)直接把這做爲惟一方案。當消息機制成爲線程間通訊的惟一方案時,線程已經再也不是線程了(共享內存算是線程的比較核心的特徵了),所以dart自稱單線程語言,其isolate是區別於多線程的一種併發編程機制。

用通訊代替共享內存是個大的思路,實際上衍生出了多重併發編程模型如Actor、CSP等。這裏就再也不更細地分析了,能夠參考erlang/go的實現。

參考:

併發編程模型:事件驅動 vs 線程

如何理解 Golang 中「不要經過共享內存來通訊,而應該經過通訊來共享內存」?

多線程和異步

比較早的時候,是不多用異步調用的。要髮網絡請求的時候,就開個子線程讓子線程進行同步調用,阻塞等待調用結果。

這種方式稱爲同步,期間這個線程是阻塞的。

顯然,這是對線程資源極大的浪費。所以如今這樣作的少了,一般發出網絡請求後當前線程會繼續往下執行,請求回包後會經過某種事件處理機制觸發回調函數進行處理。

這種方式稱爲異步,期間這個線程是非阻塞的。

除了網絡請求,IO讀寫等場景也是相似的。這些不佔用CPU的場景應當優先使用異步的手段,而不是開子線程處理。(iOS平臺下好像基本上沒有這樣作的,之前寫Java的時候見到的比較多)

iOS下,一般咱們用block或delegate進行異步編程,寫起來會比同步代碼麻煩一些。協程的一大好處就是可以以相似同步代碼的方式寫異步代碼。對應的async/await是swift最受人期待的新特性,以前一度傳言swift5會上,惋惜並無...

相關文章
相關標籤/搜索