1. 程式人生 > >多執行緒程式設計4

多執行緒程式設計4

一、簡介

在iOS所有實現多執行緒的方案中,GCD應該是最有魅力的,因為GCD本身是蘋果公司為多核的並行運算提出的解決方案。GCD在工作時會自動利用更多的處理器核心,以充分利用更強大的機器。GCD是Grand Central Dispatch的簡稱,它是基於C語言的。如果使用GCD,完全由系統管理執行緒,我們不需要編寫執行緒程式碼。只需定義想要執行的任務,然後新增到適當的排程佇列(dispatch queue)。GCD會負責建立執行緒和排程你的任務,系統直接提供執行緒管理

二、排程佇列(dispath queue)

1.GCD的一個重要概念是佇列,它的核心理念:將長期執行的任務拆分成多個工作單元,並將這些單元新增到dispath queue

中,系統會為我們管理這些dispath queue,為我們在多個執行緒上執行工作單元,我們不需要直接啟動和管理後臺執行緒。

2.系統提供了許多預定義的dispath queue,包括可以保證始終在主執行緒上執行工作的dispath queue。也可以建立自己的dispath queue,而且可以建立任意多個。GCD的dispath queue嚴格遵循FIFO(先進先出)原則,新增到dispath queue的工作單元將始終按照加入dispath queue的順序啟動。

3.dispatch queue按先進先出的順序,序列或併發地執行任務

1> serial dispatch queue一次只能執行一個任務, 當前任務完成才開始出列並啟動下一個任務

2> concurrent dispatch queue則儘可能多地啟動任務併發執行

三、建立和管理dispatch queue

1.獲得全域性併發Dispatch Queue (concurrent dispatch queue)

1> 併發dispatch queue可以同時並行地執行多個任務,不過併發queue仍然按先進先出的順序來啟動任務。併發queue會在之前的任務完成之前就出列下一個任務並開始執行。併發queue同時執行的任務數量會根據應用和系統動態變化,各種因素包括:可用核數量、其它程序正在執行的工作數量、其它序列dispatch queue中優先任務的數量等.

2> 系統給每個應用提供三個併發dispatch queue,整個應用內全域性共享,三個queue的區別是優先順序。你不需要顯式地建立這些queue,使用dispatch_get_global_queue函式來獲取這三個queue:

// 獲取預設優先順序的全域性併發dispatch queue
dispatch_queue_t  queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
第一個引數用於指定優先順序,分別使用DISPATCH_QUEUE_PRIORITY_HIGH和DISPATCH_QUEUE_PRIORITY_LOW兩個常來獲取高和低優先順序的兩個queue;第二個引數目前未使用到,預設0即可

3> 雖然dispatch queue是引用計數的物件,但你不需要retain和release全域性併發queue。因為這些queue對應用是全域性的,retain和release呼叫會被忽略。你也不需要儲存這三個queue的引用,每次都直接呼叫dispatch_get_global_queue獲得queue就行了。

2.建立序列Dispatch Queue (serial dispatch queue)

1> 應用的任務需要按特定順序執行時,就需要使用序列Dispatch Queue,序列queue每次只能執行一個任務。你可以使用序列queue來替代鎖,保護共享資源 或可變的資料結構。和鎖不一樣的是,序列queue確保任務按可預測的順序執行。而且只要你非同步地提交任務到序列queue,就永遠不會產生死鎖

2> 你必須顯式地建立和管理所有你使用的序列queue,應用可以建立任意數量的序列queue,但不要為了同時執行更多工而建立更多的序列queue。如果你需要併發地執行大量任務,應該把任務提交到全域性併發queue

3> 利用dispatch_queue_create函式建立序列queue,兩個引數分別是queue名和一組queue屬性

dispatch_queue_t queue;
queue = dispatch_queue_create("cn.itcast.queue", NULL);

3.執行時獲得公共Queue
GCD提供了函式讓應用訪問幾個公共dispatch queue:

1> 使用dispatch_get_current_queue函式作為除錯用途,或者測試當前queue的標識。在block物件中呼叫這個函式會返回block提交到的queue(這個時候queue應該正在執行中)。在block物件之外呼叫這個函式會返回應用的預設併發queue。
2> 使用dispatch_get_main_queue函式獲得應用主執行緒關聯的序列dispatch queue
3> 使用dispatch_get_global_queue來獲得共享的併發queue

4.Dispatch Queue的記憶體管理

1> Dispatch Queue和其它dispatch物件(還有dispatch source)都是引用計數的資料型別。當你建立一個序列dispatch queue時,初始引用計數為 1,你可以使用dispatch_retain和dispatch_release函式來增加和減少引用計數。當引用計數到達 0 時,系統會非同步地銷燬這個queue

2> 對dispatch物件(如dispatch queue)retain和release 是很重要的,確保它們被使用時能夠保留在記憶體中。和OC物件一樣,通用的規則是如果使用一個傳遞過來的queue,你應該在使用前retain,使用完之後release

3> 你不需要retain或release全域性dispatch queue,包括全域性併發dispatch queue和main dispatch queue

4> 即使你實現的是自動垃圾收集的應用,也需要retain和release建立的dispatch queue和其它dispatch物件。GCD 不支援垃圾收集模型來回收記憶體

四、新增任務到queue

要執行一個任務,你需要將它新增到一個適當的dispatch queue,你可以單個或按組來新增,也可以同步或非同步地執行一個任務,也。一旦進入到queue,queue會負責儘快地執行你的任務。一般可以用一個block來封裝任務內容。

1.新增單個任務到queue

1> 非同步新增任務

你可以非同步或同步地新增一個任務到Queue,儘可能地使用dispatch_async或dispatch_async_f函式非同步地排程任務。因為新增任務到Queue中時,無法確定這些程式碼什麼時候能夠執行。因此非同步地新增block或函式,可以讓你立即排程這些程式碼的執行,然後呼叫執行緒可以繼續去做其它事情。特別是應用主執行緒一定要非同步地 dispatch 任務,這樣才能及時地響應使用者事件

2> 同步新增任務

少數時候你可能希望同步地排程任務,以避免競爭條件或其它同步錯誤。 使用dispatch_sync和dispatch_sync_f函式同步地新增任務到Queue,這兩個函式會阻塞當前呼叫執行緒,直到相應任務完成執行。注意:絕對不要在任務中呼叫 dispatch_sync或dispatch_sync_f函式,並同步排程新任務到當前正在執行的 queue。對於序列queue這一點特別重要,因為這樣做肯定會導致死鎖;而併發queue也應該避免這樣做。

3> 程式碼演示

// 呼叫前,檢視下當前執行緒
NSLog(@"當前呼叫執行緒:%@", [NSThread currentThread]);

// 建立一個序列queue
dispatch_queue_t queue = dispatch_queue_create("cn.itcast.queue", NULL);

dispatch_async(queue, ^{
    NSLog(@"開啟了一個非同步任務,當前執行緒:%@", [NSThread currentThread]);
});

dispatch_sync(queue, ^{
    NSLog(@"開啟了一個同步任務,當前執行緒:%@", [NSThread currentThread]);
});
// 銷燬佇列
dispatch_release(queue);
列印資訊:
2013-02-03 09:03:37.348 thread[6491:c07] 當前呼叫執行緒:<NSThread: 0x714fa80>{name = (null), num = 1}
2013-02-03 09:03:37.349 thread[6491:1e03] 開啟了一個非同步任務,當前執行緒:<NSThread: 0x74520a0>{name = (null), num = 3}
2013-02-03 09:03:37.350 thread[6491:c07] 開啟了一個同步任務,當前執行緒:<NSThread: 0x714fa80>{name = (null), num = 1}

2.併發地執行迴圈迭代

如果你使用迴圈執行固定次數的迭代, 併發dispatch queue可能會提高效能。

例如下面的for迴圈:

int i;
int count = 10;
for (i = 0; i < count; i++) {
   printf("%d  ",i);
}

1> 如果每次迭代執行的任務與其它迭代獨立無關,而且迴圈迭代執行順序也無關緊要的話,你可以呼叫dispatch_apply或dispatch_apply_f函式來替換迴圈。這兩個函式為每次迴圈迭代將指定的block或函式提交到queue。當dispatch到併發 queue時,就有可能同時執行多個迴圈迭代。用dispatch_apply或dispatch_apply_f時你可以指定序列或併發 queue。併發queue允許同時執行多個迴圈迭代,而序列queue就沒太大必要使用了。

下面程式碼使用dispatch_apply替換了for迴圈,你傳遞的block必須包含一個size_t型別的引數,用來標識當前迴圈迭代。第一次迭代這個引數值為0,最後一次值為count - 1

// 獲得全域性併發queue
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
size_t count = 10;
dispatch_apply(count, queue, ^(size_t i) {
    printf("%zd ", i);
});
// 銷燬佇列
dispatch_release(queue);
列印資訊:
1 2 0 3 4 5 6 7 8 9 
可以看出,這些迭代是併發執行的

和普通for迴圈一樣,dispatch_apply和dispatch_apply_f函式也是在所有迭代完成之後才會返回,因此這兩個函式會阻塞當前執行緒,主執行緒中呼叫這兩個函式必須小心,可能會阻止事件處理迴圈並無法響應使用者事件。所以如果迴圈程式碼需要一定的時間執行,可以考慮在另一個執行緒中呼叫這兩個函式。如果你傳遞的引數是序列queue,而且正是執行當前程式碼的queue,就會產生死鎖

3.在主執行緒中執行任務

1> GCD提供一個特殊的dispatch queue,可以在應用的主執行緒中執行任務。只要應用主執行緒設定了run loop(由CFRunLoopRef型別或NSRunLoop物件管理),就會自動建立這個queue,並且最後會自動銷燬。非Cocoa應用如果不顯式地設定run loop, 就必須顯式地呼叫dispatch_main函式來顯式地啟用這個dispatch queue,否則雖然你可以新增任務到queue,但任務永遠不會被執行。

2> 呼叫dispatch_get_main_queue函式獲得應用主執行緒的dispatch queue,新增到這個queue的任務由主執行緒序列化執行

3> 程式碼實現,比如非同步下載圖片後,回到主執行緒顯示圖片

// 非同步下載圖片
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSURL *url = [NSURL URLWithString:@"http://car0.autoimg.cn/upload/spec/9579/u_20120110174805627264.jpg"];
    UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];
    
    // 回到主執行緒顯示圖片
    dispatch_async(dispatch_get_main_queue(), ^{
        self.imageView.image = image;
    });
});

4.任務中使用Objective-C物件

GCD支援Cocoa記憶體管理機制,因此可以在提交到queue的block中自由地使用Objective-C物件。每個dispatch queue維護自己的autorelease pool確保釋放autorelease物件,但是queue不保證這些物件實際釋放的時間。如果應用消耗大量記憶體,並且建立大量autorelease物件,你需要建立自己的autorelease pool,用來及時地釋放不再使用的物件。

五、暫停和繼續queue

我們可以使用dispatch_suspend函式暫停一個queue以阻止它執行block物件;使用dispatch_resume函式繼續dispatch queue。呼叫dispatch_suspend會增加queue的引用計數,呼叫dispatch_resume則減少queue的引用計數。當引用計數大於0時,queue就保持掛起狀態。因此你必須對應地呼叫suspend和resume函式。掛起和繼續是非同步的,而且只在執行block之間(比如在執行一個新的block之前或之後)生效。掛起一個queue不會導致正在執行的block停止。

六、Dispatch Group的使用

假設有這樣一個需求:從網路上下載兩張不同的圖片,然後顯示到不同的UIImageView上去,一般可以這樣實現

// 根據url獲取UIImage
- (UIImage *)imageWithURLString:(NSString *)urlString {
    NSURL *url = [NSURL URLWithString:urlString];
    NSData *data = [NSData dataWithContentsOfURL:url];
    return [UIImage imageWithData:data];
}

- (void)downloadImages {
    // 非同步下載圖片
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 下載第一張圖片
        NSString *url1 = @"http://car0.autoimg.cn/upload/spec/9579/u_20120110174805627264.jpg";
        UIImage *image1 = [self imageWithURLString:url1];
        
        // 下載第二張圖片
        NSString *url2 = @"http://hiphotos.baidu.com/lvpics/pic/item/3a86813d1fa41768bba16746.jpg";
        UIImage *image2 = [self imageWithURLString:url2];
        
        // 回到主執行緒顯示圖片
        dispatch_async(dispatch_get_main_queue(), ^{
            self.imageView1.image = image1;
            
            self.imageView2.image = image2;
        });
    });
}
雖然這種方案可以解決問題,但其實兩張圖片的下載過程並不需要按順序執行,併發執行它們可以提高執行速度。有個注意點就是必須等兩張圖片都下載完畢後才能回到主執行緒顯示圖片。Dispatch Group能夠在這種情況下幫我們提升效能。下面先看看Dispatch Group的用處:

我們可以使用dispatch_group_async函式將多個任務關聯到一個Dispatch Group和相應的queue中,group會併發地同時執行這些任務。而且Dispatch Group可以用來阻塞一個執行緒, 直到group關聯的所有的任務完成執行。有時候你必須等待任務完成的結果,然後才能繼續後面的處理。

下面用Dispatch Group優化上面的程式碼:

// 根據url獲取UIImage
- (UIImage *)imageWithURLString:(NSString *)urlString {
    NSURL *url = [NSURL URLWithString:urlString];
    NSData *data = [NSData dataWithContentsOfURL:url];
    // 這裡並沒有自動釋放UIImage物件
    return [[UIImage alloc] initWithData:data];
}

- (void)downloadImages {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    // 非同步下載圖片
    dispatch_async(queue, ^{
        // 建立一個組
        dispatch_group_t group = dispatch_group_create();
        
        __block UIImage *image1 = nil;
        __block UIImage *image2 = nil;
        
        // 關聯一個任務到group
        dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            // 下載第一張圖片
            NSString *url1 = @"http://car0.autoimg.cn/upload/spec/9579/u_20120110174805627264.jpg";
            image1 = [self imageWithURLString:url1];
        });
        
        // 關聯一個任務到group
        dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            // 下載第一張圖片
            NSString *url2 = @"http://hiphotos.baidu.com/lvpics/pic/item/3a86813d1fa41768bba16746.jpg";
            image2 = [self imageWithURLString:url2];
        });
        
        // 等待組中的任務執行完畢,回到主執行緒執行block回撥
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            self.imageView1.image = image1;
            self.imageView2.image = image2;
            
            // 千萬不要在非同步執行緒中自動釋放UIImage,因為當非同步執行緒結束,非同步執行緒的自動釋放池也會被銷燬,那麼UIImage也會被銷燬
            
            // 在這裡釋放圖片資源
            [image1 release];
            [image2 release];
        });
        
        // 釋放group
        dispatch_release(group);
    });
}
dispatch_group_notify函式用來指定一個額外的block,該block將在group中所有任務完成後執行

相關推薦

執行程式設計4

一、簡介 在iOS所有實現多執行緒的方案中,GCD應該是最有魅力的,因為GCD本身是蘋果公司為多核的並行運算提出的解決方案。GCD在工作時會自動利用更多的處理器核心,以充分利用更強大的機器。GCD是Grand Central Dispatch的簡稱,它是基於C語言的。如果使

iOS-GCD執行程式設計4

                     iOS-GCD多執行緒程式設計詳解4 今天主要討論的是dispatch_group_t及其它的使用。 一.dispatch_qroup_t dispatch_group_t就是提交的任務的一個集合,非同步呼叫的block集合

IOS_執行程式設計4

一、簡介 在iOS所有實現多執行緒的方案中,GCD應該是最有魅力的,因為GCD本身是蘋果公司為多核的並行運算提出的解決方案。GCD在工作時會自動利用更多的處理器核心,以充分利用更強大的機器。GCD是Grand Central Dispatch的簡稱,它是基於C語言的。如

python執行程式設計(4): 死鎖和可重入鎖

線上程間共享多個資源的時候,如果兩個執行緒分別佔有一部分資源並且同時等待對方的資源,就會造成死鎖。儘管死鎖很少發生,但一旦發生就會造成應用的停止響應。下面看一個死鎖的例子: # encoding: UTF-8import threadingimport timec

《Java執行程式設計實戰》——第4章 Guarded Suspension(保護性暫掛)模式

一個執行緒等待另一個執行緒完成一定的操作,才能繼續執行。 核心思想是如果某個執行緒執行特定的操作前需要滿足一定的條件,則在該條件未滿足時將該執行緒暫停執行(waiting)。 類圖 如果頻繁出現保護方法被呼叫時保護條件不成立,那麼保護方法的執行執行緒就會頻繁地被暫掛和喚醒,而導致頻繁

Linux c執行程式設計4個例項

在主流的作業系統中,多工一般都提供了程序和執行緒兩種實現方式,程序享有獨立的程序空間,而執行緒相對於程序來說是一種更加輕量級的多工並行,多執行緒之間一般都是共享所在程序的記憶體空間的。   Linux也不例外,雖然從核心的角度來看,執行緒體現為一種對程序的"克隆"(clon

ava執行程式設計-(4)-執行間通訊機制的介紹與使用

原文出自 : https://blog.csdn.net/xlgen157387/article/details/78195817 執行緒間通訊簡介 我們知道執行緒是作業系統中獨立的個體,但是這個單獨的個體之間沒有一種特殊的處理方式使之成為一個整體,執行緒之間沒有任何交

C#執行程式設計筆記(4.4)-處理Task任務中的異常

近來在學習Eugene Agafonov編寫的《C#多執行緒程式設計實戰》(譯),做些筆記也順便分享一下^-^ using System; using System.Threading.Tasks; using System.Threading; namespace 處理任

c#執行程式設計筆記4

a)使用Monitor類 Monitor類提供了鎖定部分程式碼的簡單機制,只要把受保護的程式碼包裝在Monitor.Enter與Monitor.Exit程式碼塊中就行了。Monitor.Enter方法與Monitor.Exit方法都有一個引數。 Monitor.Enter(o

執行程式設計入門(4):wait,notify方法使用注意事項

1 問題 2 程式碼 public class WaitAndNotify1 { public static void main(String[] args) { Thread t1=new Thread(new Runn

執行程式設計10例問題(4

問題:VS2012中MFC,選中控制元件,右鍵,選擇新增變數無反應。 原因:對話方塊名字發生變化導致。 解決方案:在解決方案資源管理器中的資原始檔下找到MultiThread3.rc2,雙擊進入資源檢視,在Diaog下選中對話方塊,在螢幕右側會出現此對話方塊

執行程式設計學習4——WaitForSingleObject

DWORD WaitForSingleObject(HANDLE hHandle,DWORD dwMilliseconds); hHandle為要監視的物件(一般為同步物件,也可以是執行緒)的控制代碼; dwMilliseconds為hHandle物件所設定的超時值,單位為毫

Java執行程式設計執行的同步與互斥/執行安全/Java鎖

摘要:多執行緒三個特徵:原子性、可見性以及有序性.&gt;執行緒的同步與互斥?(同步執行緒與非同步執行緒,執行緒同步和非同步問題)&nbsp;&nbsp;1.同步:假設現有執行緒A和執行緒B,執行緒A需要往緩衝區寫資料,執行緒B需要從緩衝區讀資料,但他們之間存在一種制約

Linux執行程式設計---執行間同步(互斥鎖、條件變數、訊號量和讀寫鎖)

本篇博文轉自http://zhangxiaoya.github.io/2015/05/15/multi-thread-of-c-program-language-on-linux/ Linux下提供了多種方式來處理執行緒同步,最常用的是互斥鎖、條件變數、訊號量和讀寫鎖。  下面是思維導

java執行程式設計詳細入門教程

##1、概念      執行緒是jvm排程的最小單元,也叫做輕量級程序,程序是由執行緒組成,執行緒擁有私有的程式技術器以及棧,並且能夠訪問堆中的共享資源。這裡提出一個問題,為什麼要用多執行緒?有一下幾點,首先,隨著cpu核心數的增加,計算機硬

Python Threading 執行程式設計

寫在篇前   threading模組是python多執行緒處理包,使用該模組可以很方便的實現多執行緒處理任務,本篇文章的基礎是需要掌握程序、執行緒基本概念,對PV原語、鎖等傳統同步處理方法有一定的瞭解。另外,threading模組的實現是參考java多執行緒處理方式,並且只實現了其中的一

MFC執行程式設計實踐總結之AfxBeginThread()

在MFC多執行緒程式設計中,執行緒函式呼叫類內成員變數和成員函式的步驟: 1.將執行緒函式在類.h檔案中類內宣告,並用修飾符static修飾; class CtestDlg : public CDialogEx {    public:    &nbs

DEVOPS-01程序、執行程式設計

一、多執行緒程式設計 1.1 forking工作原理 1.1.1 什麼是forking 1. fork(分岔)在Linux系統中使用非常廣泛 2.  當某一命令執行時,父程序(當前程序)fork出一個子程序 3.  父程序將自身資源拷貝一份,命令在子程序中執行時,就具

java執行程式設計之使用Synchronized塊同步變數

通過synchronized塊來同步特定的靜態或非靜態方法。 要想實現這種需求必須為這些特性的方法定義一個類變數,然後將這些方法的程式碼用synchronized塊括起來,並將這個類變數作為引數傳入synchronized塊   下面的程式碼演示瞭如何同步特定的類方法:

執行基礎4 同步與通訊

 1.什麼情況下需要同步 當多執行緒併發執行同一程式碼時 希望某一段程式碼執行的過程中CPU不要切換到其他執行緒工作. 這時就需要同步.   2.同步程式碼塊 使用synchronized關鍵字加上一個鎖物件來定義一段程式碼, 這就叫同步程式碼塊 多個同步程式碼塊