原文
何為並發 何為多線程
操作系統中正在運行的一個應用程序都會有一個獨立的進程,每個進程之間是獨立的,每個進程均運行在其專用且受保護的內存空間內。一個進程要想執行任務,必須得有線程(每1個進程至少要有1條線程),線程是進程的基本執行單元,一個進程(程序)的所有任務都在線程中執行。一個進程內可以有多個線程,這些線程作為操作系統調度的最小單元,負責執行各種各樣的任務,這些線程都擁有各自的計數器、堆棧、局部變量等屬性,並且可以訪問共享內存,在一個線程內順序的執行CUP無分叉的命令列。
當前常見的編程語言開發的程序,開發語言只是為了使程序員更好的操作,實質上,對計算機而言,程序會轉換成其能理解的匯編語言進而解釋成機器碼來執行。機器碼是按順序執行的,一個復雜的多步操作只能一步步按順序逐個執行。因此才有了多線程和多核CPU等技術的使用。
對於單核處理器,可以將多個步驟放到不同的線程,這樣一來用戶完成UI操作後其他後續任務在其他線程中,當CPU空閒時會繼續執行,而此時對於用戶而言可以繼續進行其他操作。
對於多核處理器,如果用戶在UI線程中完成某個操作之後,其他後續操作在別的線程中繼續執行,用戶同樣可以繼續進行其他UI操作,與此同時前一個操作的後續任務可以分散到多個空閒CPU中繼續執行(當然具體調度順序要根據程序設計而定),既解決了線程阻塞又提高了運行效率。蘋果從雙核A5處理器後又在A7中加入了協處理器,優化性能不只是在多線程,還有處理器的性能。
並發是一種現象,一種經常出現,無可避免的現象。它描述的是“多個任務同時發生,需要被處理”這一現象。它的側重點在於“發生”。並行指的是一種技術,一個同時處理多個任務的技術。它描述了一種能夠同時處理多個任務的能力,側重點在於“運行”。並行的反義詞就是串行,表示任務必須按順序來,一個一個執行,前一個執行完了才能執行後一個。多線程,正是采用了並行技術,從而提高了執行效率。因為有多個線程,所以計算機的多個CPU可以同時工作,同時處理不同線程內的指令。創建多個線程,真正加快程序運行速度的,是並行技術。也就是讓多個CPU同時工作。
在低層,GCD全局dispatch queue僅僅是工作線程池的抽象。這些隊列中的Block一旦可用,就會被dispatch到工作線程中。提交至用戶隊列的Block最終也會通過全局隊列進入相同的工作線程池(除非你的用戶隊列的目標是主線程,但是為了提高運行速度,我們絕不會這麼干)。有兩種途徑來通過GCD“搾取”多核心系統的性能:將單一任務或者一組相關任務並發至全局隊列中運算;將多個不相關的任務或者關聯不緊密的任務並發至用戶隊列中運算。
線程小例
線程 start 後操作系統會給他分配相關的資源,包括單獨的程序計數器和棧。操作系統會把這個線程作為一個獨立的個體進行調度,分配時間片讓它執行。線程被 CPU 調度後就會執行線程中的方法。
以NSThread為例:
@interface ViewController () @property (nonatomic, strong) NSThread *thread; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; //創建線程 NSThread *thread=[[NSThread alloc]initWithTarget:self selector:@selector(test) object:nil]; //設置線程的名稱 [thread setName:@"線程A"]; self.thread = thread; } //當手指按下的時候,開啟線程 -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { //開啟線程 [self.thread start]; } -(void)test { //獲取線程 NSThread *current=[NSThread currentThread]; NSLog(@"test---打印線程---%@",self.thread.name); NSLog(@"test---線程開始---%@",current.name); //設置線程阻塞,阻塞秒 NSLog(@"接下來,線程阻塞0.5秒"); [NSThread sleepForTimeInterval:.5]; //第二種設置線程阻塞,以當前時間為基准阻塞秒 NSLog(@"接下來,線程阻塞0.5秒"); NSDate *date=[NSDate dateWithTimeIntervalSinceNow:.5]; [NSThread sleepUntilDate:date]; for (int i= 0; i<10; i++) { NSLog(@"線程--%d--%@",i,current.name); if (9==i) { //結束線程 (線程死了不能復生,如果在線程死亡之後,再次點擊屏幕嘗試重新開啟線程,則程序會掛) [NSThread exit]; } } NSLog(@"test---線程結束---%@",current.name); } @end
sleep方法使當前所在線程進入阻塞,只是讓出 CPU ,並沒有釋放對象鎖。由於休眠時間結束後不一定會立即被 CPU 調度,因此線程休眠的時間可能大於傳入參數。
多線程中的內存析構
一個現代計算機通常由兩個或者多個 CPU,每個 CPU 都包含一系列的寄存器,CPU 在寄存器上執行操作的速度遠大於在主存上執行的速度。每個 CPU 有一個 CPU 緩存層。CPU 訪問緩存層的速度快於訪問主存的速度,但通常比訪問內部寄存器的速度還要慢一點。
通常情況下,當一個 CPU 需要讀取主存時,它會將主存的部分讀到 CPU 緩存中。它甚至可能將緩存中的部分內容讀到它的內部寄存器中,然後在寄存器中執行操作。當 CPU 需要將結果寫回到主存中去時,它會將內部寄存器的值刷新到緩存中,然後在某個時間點將值刷新回主存。
內存中分為堆和棧:堆為所有線程共享,存放運行時創建的對象和數組數據;棧為每個線程獨有,棧中存放了當前方法的調用信息以及基本數據類型和引用類型的數據。
堆占用的內存由系統回收,堆中包含在程序中創建的所有對象,無論是哪一個線程創建的。一個對象的成員變量隨著這個對象自身存放在堆上。不管這個成員變量是基本類型還是引用類型。靜態成員變量跟隨著類定義一起也存放在堆上。
棧在線程創建時創建,在一個方法中,你創建的局部變量和部分結果都會保存在棧中,並在方法調用和返回中起作用。當前棧只對當前線程可見。即使兩個線程執行同樣的代碼,這兩個線程仍然會在自己的線程棧中創建一份本地副本。因此,每個線程擁有每個本地變量的獨有版本。棧中保存方法調用棧、基本類型的數據、以及對象的引用。
槽與坑
同一時間,一個CPU只能處理1條線程,只有1條線程在工作(執行)。多線程並發(同時)執行,其實是CPU快速地在多條線程之間調度(切換)。如果CPU調度線程的時間足夠快,就造成了多線程並發執行的假象。如果線程非常非常多,CPU會在N多線程之間調度,CPU會累死,消耗大量的CPU資源,每條線程被調度執行的頻次會降低(線程的執行效率降低)。多個線程之間來回切換,意味著有多組棧和寄存器中的值需要不斷地被備份、替換。把多個任務放在一個線程裡,按順序執行。只有一組寄存器和棧存在,顯然效率更好一些,但也因此帶來了線程阻塞。
有句老話說來著:“真正提高效率的是並行而不單單是多線程”。