每個NSThread對象對應一個線程,真正最原始的線程。
優點:輕量級最低,相對簡單。
缺點:手動管理所有的線程活動,如生命周期、線程同步、睡眠等。
自帶線程管理的抽象類。
優點:自帶線程周期管理,操作上可更注重自己邏輯。
缺點:面向對象的抽象類,只能實現它或者使用它定義好的兩個子類
NSInvocationOperation 和 NSBlockOperation。
蘋果開發的一個多核心編程的解決方法。開發者不需要再跟線程打交道了,只需要向隊列中添加代碼塊即可。GCD在後端管理著一個線程池。GCD不僅僅決著你的代碼塊將在哪個線程被執行,並且他還可以根據可用的系統資源對這些線程進行管理。
優點:最高效,避免並發陷阱。
缺點:基於C實現。
這裡,主要記錄一下GCD的使用。
使用GCD需要考慮的有:任務的執行方法,隊列的運行方式。
任務:表示一段執行的代碼,對應到代碼裡就是一個block。
隊列:1.隊列當中放的是各種待執行的任務。
串行隊列:一次只執行一個任務。 並行隊列:能夠同時執行多個任務,系統會維護一個線程池來保證並行隊列的執行。線程池會根據當前任務量自行安排線程的數量,以確保任務盡快執行。隊列對應到代碼裡是一個dispatch queue t對象。可能在非ARC下我們需要手動管理內存,但是個人覺得那些逝去的技術,就讓他沉睡在過去吧,況且我也並不想手動的去管理這些與開發不相關的東西,讓ARC替我去搞定吧,哈哈。當然內存管理是很重要的,之後我也會再在內存管理方面進行深入的學習,並開一篇關於內存管理的文章。言歸正傳,ARC下通常隊列聲明為strong就ok。
異步(加入隊列的任務會在另外的線程中執行,一旦任務被提交到隊列,就立刻返回)
GCD當中的兩個異步的API
void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
voiddispatch_async_f(dispatch_queue_tqueue,void*context,dispatch_function_twork);
第一個API的任務以block的方式加入隊列當中,第二個API以函數的形式把任務加入隊列當中。二者在執行功能上並沒有什麼區別,都是將任務提交到隊列當中就立即返回。
代碼示例
NSLog(@"this is main queue, i want to throw a task to global queue");
dispatch_queue_t globalQueue = dispatch_queue_create("com.liancheng.global_queue", DISPATCH_QUEUE_SERIAL);
dispatch_async(globalQueue, ^{
// task
});
NSLog(@"this is main queue, throw task completed");
同步(任務被加入隊列之後會阻塞主線程,不會立即返回,需等任務完成之後再返回)
GCD當中的兩個同步API
void dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
void dispatch_sync_f(dispatch_queue_t queue, void *context, dispatch_function_t work);
代碼示例
NSLog(@"thisismainqueue,iwanttothrowatasktoglobalqueue");
dispatch_queue_tglobalQueue=dispatch_queue_create("com.liancheng.global_queue",DISPATCH_QUEUE_SERIAL);
dispatch_async(globalQueue,^{
//task
});
NSLog(@"thisismainqueue,throwtaskcompleted");
GCD中的死鎖情況
dispatch_queue_tqueue=dispatch_queue_create("com.liancheng.serial_queue",DISPATCH_QUEUE_SERIAL);
dispatch_async(queue,^{
//到達串行隊列
dispatch_sync(queue,^{//發生死鎖
});
});
在串行隊列當中的一個任務,以同步方式提交了一個任務到自己的隊列當中,造成鎖。
眾所周知,串行隊列中的任務一個一個執行,當執行任務的過程當中,A任務向自己這個隊列當中添加了一個B任務,並且還是以同步方式提交,同步方式提交的B任務就要立刻執行(因為是同步嘛),可是A任務還沒有執行完畢,由此造成死鎖。
如果隊列是並行隊列,就不會發生這種情況了。或者把B任務加到其他的隊列當中。
dispatch_queue_tdispatch_get_main_queue(void)
dispatch_queue_tdispatch_get_global_queue(longidentifier,unsignedlongflags);
這種方式獲取的隊列都市並行隊列,並且隊列不能修改。
identifier用來標識隊列優先級,可以傳入兩種類型的枚舉作為參數。通常傳入0,代表隊列的優先級是默認優先級,處於高優先級和低優先級之間。 flags:預留字段,傳入任何非0得值都可能導致返回NULL。
dispatch_queue_tdispatch_queue_create(constchar*label,dispatch_queue_attr_tattr);
label: 隊列的名稱,調試的時候可以區分其他的隊列 attr: 隊列的屬性,dispatchqueueattrt類型。用以標識隊列串行,並行,以及優先級等信息
使用dispatchbarrier將任務加入到並行隊列之後,任務會在前面任務全部執行完成之後執行,任務執行過程中,其他任務無法執行,直到barrier任務執行完成。
4個API
voiddispatch_barrier_async(dispatch_queue_tqueue,dispatch_block_tblock);
voiddispatch_barrier_async_f(dispatch_queue_tqueue,void*context,dispatch_function_twork);
voiddispatch_barrier_sync(dispatch_queue_tqueue,dispatch_block_tblock);
voiddispatch_barrier_sync_f(dispatch_queue_tqueue,void*context,dispatch_function_twork);
(以下來自Objc中國)
使用並發編程會帶來許多陷阱。只要一旦你做的事情超過了最基本的情況,對於並發執行的多任務之間的相互影響的不同狀態的監視就會變得異常困難。
並發編程的許多問題的根源就是在多個線程當中訪問共享資源。 資源可以是一個屬性,通用的內存,網絡設備或者一個文件等等。
以一張圖片說明資源共享所引發的問題。
在實際的開發中,情況甚至要比上面的情況更加復雜,因為現代 CPU 為了優化目的,往往會改變向內存讀寫數據的順序(亂序執行)。
互斥訪問的意思就是同一時刻,只允許一個線程訪問某個特定資源。為了保證這一點,每個希望訪問共享資源的線程,首先需要獲得一個共享資源的互斥鎖,一旦某個線程對資源完成了操作,就釋放掉這個互斥鎖,這樣別的線程就有機會訪問該共享資源了。
雖然出於安全性的考慮,Objective-C將所有的屬性都默認設置為原子操作,是的所有的屬性都能支持互斥鎖。(atomic,在之後的關於多線程的學習中會深入討論原子操作)。但是要知道加鎖的過程是需要昂貴的代價的。
資源上的枷鎖會引發一定的性能代價。 獲取鎖和釋放鎖本身也需要沒有竟態條件。 當某個線程因為等待其他線程釋放鎖的過程當中會處於睡眠狀態,並且當其他線程用完鎖之後,睡眠的線程會接收到通知,這些過程都是非常昂貴且復雜的。所以,在編程當中,不需要多線程的情況下,並且,即使是在多線程的情況下,如果能確認不會引發資源共享的情況發生,也盡量不要使用原子操作。
互斥鎖解決了競態條件的問題,但很不幸同時這也引入了一些其他問題,其中一個就是死鎖。當多個線程在相互等待著對方的結束時,就會發生死鎖,這時程序可能會被卡住。
一個資源在兩個或者兩個以上的線程當中相互使用,第三方線程始終得不到這部分資源。 這部分待之後理解
低優先級的任務先獲取到共享資源的鎖,高優先級的任務需要等待低優先級的把鎖解開後才能去訪問共享資源,如果在這個等待過程當中,有一個中優先級的任務不需要塊共享資源,那麼他就有可能會槍戰低優先級的任務而先於高優先級的任務被執行。由於低優先級的任務持有鎖,然而他又被中優先級的任務阻塞無法釋放鎖,高優先級的任務就一直無法被執行,從而能導致優先級翻轉。
所以,我們說盡量不要使用不同的優先級,使用的優先級越多,情況就越不好控制,可能出現的問題就越多。