說起多線程編程,大家應該都不陌生,從字面上理解,就是利用多線程技術(shù)編程。那么線程又是什么呢?今天我們就一起來探討一下。
1.何為線程?
從程序執(zhí)行的角度來說,1個CPU執(zhí)行的CPU命令列為一條無分叉路徑,稱之為線程。
乍一看去可能有點繞。我們從操作系統(tǒng)的角度說起。在iOS或者MacOS中,當我們啟動一個應用程序時,OS會首先將程序中的CPU指令配置到內(nèi)存中。CPU從應用程序指定的地址開始,逐行執(zhí)行CPU指令。
舉例:在調(diào)用sum方法時,添加調(diào)試斷點,運行。
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[self sumA:9 andB:2];
}
- (int)sumA:(int)a andB:(int)b
{
int c = a + b;
return c;
}
查看反匯編
0x105499823 <+51>: movq -0x8(%rbp), %rsi
0x105499827 <+55>: movq 0x4c12(%rip), %rdi ; "sumA:andB:"
0x10549982e <+62>: movq %rdi, -0x28(%rbp)
0x105499832 <+66>: movq %rsi, %rdi
0x105499835 <+69>: movq -0x28(%rbp), %rsi
0x105499839 <+73>: movl $0x9, %edx
0x10549983e <+78>: movl $0x2, %ecx
0x105499843 <+83>: callq *0x27c7(%rip) ; (void *)0x00007fff50b93780: objc_msgSend
0x105499849 <+89>: movl %eax, -0x2c(%rbp)
0x10549984c <+92>: addq $0x30, %rsp
0x105499850 <+96>: popq %rbp
0x105499851 <+97>: retq
此刻我們就很清楚的看到,程序先執(zhí)行 地址0x105499823的命令movq,接著向后移動,執(zhí)行下一條指令,即0x105499827的命令movq。就這樣不斷地執(zhí)行下去。
有一種比較特殊的命令,比如if和for語句等控制語句或函數(shù)調(diào)用的情況下,執(zhí)行命令行的地址會遠離當前的位置即位置遷移。但是,由于一個CPU一次只能執(zhí)行一條命令,不能同時執(zhí)行某處分開的并列的兩個命令,因此通過CPU執(zhí)行的CPU指令就好比一條無分差的大道,其執(zhí)行不會出現(xiàn)分歧。如圖所示:

理解了上述過程,回過頭來,再看我們開頭提到線程的概念,相信大家應該已經(jīng)理解了:線程就是一個CPU執(zhí)行CPU指令的無分叉路徑。
2.何為多線程編程?
現(xiàn)在一個物理的CPU芯片實際上有64個(64核)CPU,如果一個CPU核虛擬為兩個CPU核工作,那么一臺計算機上使用多個CPU核就是理所當然的事了。盡管如此,“1個CPU執(zhí)行的CPU指令為一條無分叉路徑”仍然不變。
這種無分叉的路徑不止1條,存在多條時,即為”多線程“。在多線程中,1個CPU核執(zhí)行多條不同路徑上的不同命令。如圖所示:

雖然CPU的相關(guān)技術(shù)有很多,其進步也令人眼花繚亂,但基本上1個CPU核一次能夠執(zhí)行的CPU命令始終為1。
那么怎樣才能在多條路徑中執(zhí)行CPU指令呢?
在iOS和MacOS中,其內(nèi)核XNU在發(fā)生操作系統(tǒng)事件時(如每隔一定時間,喚起系統(tǒng)調(diào)用等情況)會切換執(zhí)行路徑。執(zhí)行中路徑的狀態(tài),例如CPU的寄存器等信息保存到各自路徑專用的內(nèi)存塊中,從切換目標路徑專用的內(nèi)存塊中,復原CPU寄存器的信息,繼續(xù)執(zhí)行切換路徑的CPU指令。這稱之為“上下文切換”。
由于使用多線程的程序可以在某個線程和其他線程之間反復多次進行上下文切換,因此看上去就好像1個CPU核能夠并列的執(zhí)行多個線程一樣。而且在具有多個CPU核的情況下,就不是“看上去像了”,而是真的提供了多個CPU核并行執(zhí)行多個線程的技術(shù)。
這種利用多線程編程的技術(shù)就被成為“多線程編程”。
3.多線程編程的優(yōu)缺點
了解了多線程編程以后,我們來看一下多線程編程的優(yōu)缺點,首先看一下缺點,即存在什么安全隱患。
存在的安全隱患
1.數(shù)據(jù)競爭問題

多線程更新相同資源,會導致數(shù)據(jù)不一致。
這里有兩個比較經(jīng)典的案例,一個是銀行存取錢的問題,一個是賣票系統(tǒng),在后面我們會詳細講解。
2.死鎖

停止等待事件的線程會導致多個線程相互持續(xù)等待。
3.當線程過多時,會消耗大量內(nèi)存。

優(yōu)勢所在
盡管極易發(fā)生各種問題,也應當使用多線程編程。這是為什么呢?因為使用多線程編程,可以保證應用程序的響應性能。
應用程序在啟動時,通過最先執(zhí)行的線程,即“主線程”來描繪用戶界面,處理觸摸屏幕等事件。如果在該主線程中進行長時間處理,如訪問數(shù)據(jù)庫等耗時操作,就會阻塞主線程,妨礙主線程中RunLoop的執(zhí)行,從而導致不能更新用戶界面、應用程序的畫面長時間停滯,就是我們平時所說的卡頓現(xiàn)象。
這就是需要將耗時操作放在其他線程,而非主線程操作的原因。如圖所示:

將耗時操作放在其他線程中,需要回到主線程時,再回到主線程操作,就保證了用戶操作界面的流暢度。
總結(jié)
1.何為線程:線程就是 1個CPU在執(zhí)行CPU指令時的一條無分叉路徑;
2.何為多線程編程:多線程編程就是利用多線程編程的技術(shù)o(╯□╰)o;
3.多線程編程的優(yōu)缺點:
缺點:1.導致數(shù)據(jù)競爭; 2.死鎖;3.線程過多時,會大量消耗內(nèi)存;
優(yōu)點:保證應用程序的響應性能,即良好的用戶體驗。
為了用戶有更好的體驗,所以我們還是要克服種種困難,來使用多線程編程。那么如何解決剛才提到的3個問題呢,且聽下回分解。