国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

GCD

2019-11-09 17:47:56
字體:
來源:轉載
供稿:網友
  

GCD異步隊列-看過的最完整的文章了,特地轉載一下 

分類: iphone開發入門2014-04-16 11:48 498人閱讀 評論(0) 收藏 舉報

概念:

程序中同步和異步是什么意思?有什么區別?

 

解釋一:異步調用是通過使用單獨的線程執行的。原始線程啟動異步調用,異步調用使用另一個線程執行請求,而與此同時原始的線程繼續處理。同步調用則在繼續之前必須等待響應或返回值。如果不允許調用繼續即無響應或返回值,就說調用被阻塞了,不能繼續執行。解釋二:同步.一條馬路,只能開一輛車,等這個車開走了,才能開另一輛.異步.一條馬路,隨便開多少車.解釋三:就好比說,向服務器端發送請求,如果不需要服務器響應就是異步,如果需要服務器返回信息就是同步解釋四:同步,主機A發送數據的時候,主機B必須等待接收,處于阻塞狀態,這就好比別人給你打電話,你必須當場聽話,否則則【錯失良機】異步主機A發送數據的時候,主機B無須等待接收,主機B要獲得數據就從緩存里取,就好比別人給你發郵件一樣

串行執行相當于同步

并發執行相當于異步

GCD介紹(一): 基本概念和Dispatch Queue

GCD提供很多超越傳統多線程編程的優勢:

易用: GCD比之thread跟簡單易用。由于GCD基于work unit而非像thread那樣基于運算,所以GCD可以控制諸如等待任務結束、監視文件描述符、周期執行代碼以及工作掛起等任務。基于block的血統導致它能極為簡單得在不同代碼作用域之間傳遞上下文。效率: GCD被實現得如此輕量和優雅,使得它在很多地方比之專門創建消耗資源的線程更實用且快速。這關系到易用性:導致GCD易用的原因有一部分在于你可以不用擔心太多的效率問題而僅僅使用它就行了。性能: GCD自動根據系統負載來增減線程數量,這就減少了上下午切換以及增加了計算效率。

Dispatch Objects

GCD對象被稱為dispatch object。Dispatch object像Cocoa對象一樣是引用計數的。使用dispatch_release和dispatch_retain函數來操作dispatch object的引用計數來進行內存管理。

但注意不像Cocoa對象,dispatch object并不參與垃圾回收系統,所以即使開啟了ARC,你也必須手動管理GCD對象的內存。

Dispatch queues 和 dispatch sources(后面會介紹到)可以被掛起和恢復,可以有一個相關聯的任意上下文指針,可以有一個相關聯的任務完成觸發函數。

Dispatch Queues

GCD的基本概念就是dispatch queue。dispatch queue是一個對象,它可以接受任務,并將任務以先到先執行的順序來執行。dispatch queue可以是并發的或串行的。并發任務會像NSOperationQueue那樣基于系統負載來合適地并發進行,串行隊列同一時間只執行單一任務。

GCD中有三種隊列類型:

The main queue: 與主線程功能相同。實際上,提交至main queue的任務會在主線程中執行。main queue可以調用dispatch_get_main_queue()來獲得。因為main queue是與主線程相關的,所以這是一個串行隊列。Global queues: 全局隊列是并發隊列,并由整個進程共享。進程中存在三個全局隊列:高、中(默認)、低三個優先級隊列。可以調用dispatch_get_global_queue函數傳入優先級來訪問隊列。用戶隊列: 用戶隊列 (GCD并不這樣稱呼這種隊列, 但是沒有一個特定的名字來形容這種隊列,所以我們稱其為用戶隊列) 是用函數 dispatch_queue_create 創建的隊列. 這些隊列是串行的。正因為如此,它們可以用來完成同步機制, 有點像傳統線程中的mutex。

創建隊列 

要使用用戶隊列,我們首先得創建一個。調用函數dispatch_queue_create就行了。函數的第一個參數是一個標簽,這純是為了debug。Apple建議我們使用倒置域名來命名隊列,比如“com.dreamingwish.subsystem.task”。這些名字會在崩潰日志中被顯示出來,也可以被調試器調用,這在調試中會很有用。第二個參數目前還不支持,傳入NULL就行了。

提交 Job

向一個隊列提交Job很簡單:調用dispatch_async函數,傳入一個隊列和一個block。隊列會在輪到這個block執行時執行這個block的代碼。下面的例子是一個在后臺執行一個巨長的任務:

 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        [self goDoSomethingLongAndInvolved];

        NSLog(@"Done doing something long and involved");

});

dispatch_async 函數會立即返回, block會在后臺異步執行。 

當然,通常,任務完成時簡單地NSLog個消息不是個事兒。在典型的Cocoa程序中,你很有可能希望在任務完成時更新界面,這就意味著需要在主線 程中執行一些代碼。你可以簡單地完成這個任務——使用嵌套的dispatch,在外層中執行后臺任務,在內層中將任務dispatch到main queue:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        [self goDoSomethingLongAndInvolved];

        dispatch_async(dispatch_get_main_queue(), ^{

            [textField setStringValue:@"Done doing something long and involved"];

        });

});

還有一個函數叫dispatch_sync,它干的事兒和dispatch_async相同,但是它會等待block中的代碼執行完成并返回。結合 __block類型修飾符,可以用來從執行中的block獲取一個值。例如,你可能有一段代碼在后臺執行,而它需要從界面控制層獲取一個值。那么你可以使用dispatch_sync簡單辦到:

__block NSString *stringValue;

dispatch_sync(dispatch_get_main_queue(), ^{

        // __block variables aren't automatically retained

        // so we'd better make sure we have a reference we can keep

        stringValue = [[textField stringValue] copy];

});

[stringValue autorelease];

// use stringValue in the background now

我們還可以使用更好的方法來完成這件事——使用更“異步”的風格。不同于取界面層的值時要阻塞后臺線程,你可以使用嵌套的block來中止后臺線程,然后從主線程中獲取值,然后再將后期處理提交至后臺線程:

    dispatch_queue_t bgQueue = myQueue;

    dispatch_async(dispatch_get_main_queue(), ^{

        NSString *stringValue = [[[textField stringValue] copy] autorelease];

        dispatch_async(bgQueue, ^{

            // use stringValue in the background now

        });

    });

取決于你的需求,myQueue可以是用戶隊列也可以使全局隊列。

不再使用鎖(Lock)

用戶隊列可以用于替代鎖來完成同步機制。在傳統多線程編程中,你可能有一個對象要被多個線程使用,你需要一個鎖來保護這個對象:

    NSLock *lock;

訪問代碼會像這樣:

    - (id)something

    {

        id localSomething;

        [lock lock];

        localSomething = [[something retain] autorelease];

        [lock unlock];

        return localSomething;

    }

    - (void)setSomething:(id)newSomething

    {

        [lock lock];

        if(newSomething != something)

        {

            [something release];

            something = [newSomething retain];

            [self updateSomethingCaches];

        }

        [lock unlock];

    }

使用GCD,可以使用queue來替代:

    dispatch_queue_t queue;

要用于同步機制,queue必須是一個用戶隊列,而非全局隊列,所以使用usingdispatch_queue_create初始化一個。然后可以用dispatch_async 或者 dispatch_sync將共享數據的訪問代碼封裝起來:

    - (id)something

    {

        __block id localSomething;

        dispatch_sync(queue, ^{

            localSomething = [something retain];

        });

        return [localSomething autorelease];

    }

    - (void)setSomething:(id)newSomething

    {

        dispatch_async(queue, ^{

            if(newSomething != something)

            {

                [something release];

                something = [newSomething retain];

                [self updateSomethingCaches];

            }

        });

    }

 值得注意的是dispatch queue是非常輕量級的,所以你可以大用特用,就像你以前使用lock一樣。

現在你可能要問:“這樣很好,但是有意思嗎?我就是換了點代碼辦到了同一件事兒。”

實際上,使用GCD途徑有幾個好處:

平行計算: 注意在第二個版本的代碼中, -setSomething:是怎么使用dispatch_async的。調用 -setSomething:會立即返回,然后這一大堆工作會在后臺執行。如果updateSomethingCaches是一個很費時費力的任務,且調用者將要進行一項處理器高負荷任務,那么這樣做會很棒。安全: 使用GCD,我們就不可能意外寫出具有不成對Lock的代碼。在常規Lock代碼中,我們很可能在解鎖之前讓代碼返回了。使用GCD,隊列通常持續運行,你必將歸還控制權。控制: 使用GCD我們可以掛起和恢復dispatch queue,而這是基于鎖的方法所不能實現的。我們還可以將一個用戶隊列指向另一個dspatch queue,使得這個用戶隊列繼承那個dispatch queue的屬性。使用這種方法,隊列的優先級可以被調整——通過將該隊列指向一個不同的全局隊列,若有必要的話,這個隊列甚至可以被用來在主線程上執行 代碼。集成: GCD的事件系統與dispatch queue相集成。對象需要使用的任何事件或者計時器都可以從該對象的隊列中指向,使得這些句柄可以自動在該隊列上執行,從而使得句柄可以與對象自動同步。

總結

現在你已經知道了GCD的基本概念、怎樣創建dispatch queue、怎樣提交Job至dispatch queue以及怎樣將隊列用作線程同步。接下來我會向你展示如何使用GCD來編寫平行執行代碼來充分利用多核系統的性能^ ^。我還會討論GCD更深層的東西,包括事件系統和queue targeting。

GCD介紹(二): 多核心的性能

概念

為了在單一進程中充分發揮多核的優勢,我們有必要使用多線程技術(我們沒必要去提多進程,這玩意兒和GCD沒關系)。在低層,GCD全局 dispatch queue僅僅是工作線程池的抽象。這些隊列中的Block一旦可用,就會被dispatch到工作線程中。提交至用戶隊列的Block最終也會通過全局 隊列進入相同的工作線程池(除非你的用戶隊列的目標是主線程,但是為了提高運行速度,我們絕不會這么干)。

有兩種途徑來通過GCD“榨取”多核心系統的性能:將單一任務或者一組相關任務并發至全局隊列中運算;將多個不相關的任務或者關聯不緊密的任務并發至用戶隊列中運算;

全局隊列

設想下面的循環:

1

2

for(id obj in array)

    [self doSomethingIntensiveWith:obj];

假定 -doSomethingIntensiveWith是線程安全的且可以同時執行多個.一個array通常包含多個元素,這樣的話,我們可以很簡單地使用GCD來平行運算:

1

2

3

4

5

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

for(id obj in array)

    dispatch_async(queue, ^{

        [self doSomethingIntensiveWith:obj];

    });

如此簡單,我們已經在多核心上運行這段代碼了。 

當然這段代碼并不完美。有時候我們有一段代碼要像這樣操作一個數組,但是在操作完成后,我們還需要對操作結果進行其他操作:

1

2

3

for(id obj in array)

    [self doSomethingIntensiveWith:obj];

[self doSomethingWith:array];

這時候使用GCD的 dispatch_async 就悲劇了.我們還不能簡單地使用dispatch_sync來解決這個問題, 因為這將導致每個迭代器阻塞,就完全破壞了平行計算。

解決這個問題的一種方法是使用dispatch group。一個dispatch group可以用來將多個block組成一組以監測這些Block全部完成或者等待全部完成時發出的消息。使用函數 dispatch_group_create來創建,然后使用函數dispatch_group_async來將block提交至一個dispatch queue,同時將它們添加至一個組。所以我們現在可以重新代碼:

1

2

3

4

5

6

7

8

9

10

dispatch_queue_t queue = dispatch_get_global_qeueue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_group_t group = dispatch_group_create();

for(id obj in array)

    dispatch_group_async(group, queue, ^{

        [self doSomethingIntensiveWith:obj];

    });

dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

dispatch_release(group);

 

[self doSomethingWith:array];

如果這些工作可以異步執行,那么我們可以更風騷一點,將函數-doSomethingWith:放在后臺執行。我們使用dispatch_group_async函數建立一個block在組完成后執行:

1

2

3

4

5

6

7

8

9

10

dispatch_queue_t queue = dispatch_get_global_qeueue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_group_t group = dispatch_group_create();

for(id obj in array)

    dispatch_group_async(group, queue, ^{

        [self doSomethingIntensiveWith:obj];

    });

dispatch_group_notify(group, queue, ^{

    [self doSomethingWith:array];

});

dispatch_release(group);

不僅所有數組元素都會被平行操作,后續的操作也會異步執行,并且這些異步運算都會將程序的其他部分考慮在內。注意如果-doSomethingWith:需要在主線程中執行,比如操作GUI,那么我們只要將main queue而非全局隊列傳給dispatch_group_notify函數就行了。

 

對于同步執行,GCD提供了一個簡化方法叫做dispatch_apply。這個函數調用單一block多次,并平行運算,然后等待所有運算結束,就像我們想要的那樣:

1

2

3

4

5

dispatch_queue_t queue = dispatch_get_global_qeueue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    dispatch_apply([array count], queue, ^(size_t index){

        [self doSomethingIntensiveWith:[array objectAtIndex:index]];

    });

    [self doSomethingWith:array];

這很棒,但是異步咋辦?dispatch_apply函數可是沒有異步版本的。但是我們使用的可是一個為異步而生的API啊!所以我們只要用dispatch_async函數將所有代碼推到后臺就行了:

1

2

3

4

5

6

7

dispatch_queue_t queue = dispatch_get_global_qeueue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_async(queue, ^{

    dispatch_apply([array count], queue, ^(size_t index){

        [self doSomethingIntensiveWith:[array objectAtIndex:index]];

    });

    [self doSomethingWith:array];

});

簡單的要死!

 

這種方法的關鍵在于確定我們的代碼是在一次對不同的數據片段進行相似的操作。如果你確定你的任務是線程安全的(不在本篇討論范圍內)那么你可以使用GCD來重寫你的循環了,更平行更風騷。

要看到性能提升,你還得進行一大堆工作。比之線程,GCD是輕量和低負載的,但是將block提交至queue還是很消耗資源的——block需要 被拷貝和入隊,同時適當的工作線程需要被通知。不要將一張圖片的每個像素作為一個block提交至隊列,GCD的優點就半途夭折了。如果你不確定,那么請 進行試驗。將程序平行計算化是一種優化措施,在修改代碼之前你必須再三思索,確定修改是有益的(還有確保你修改了正確的地方)。

Subsystem并發運算

前面的章節我們討論了在程序的單個subsystem中發揮多核心的優勢。下來我們要跨越多個子系統。

例如,設想一個程序要打開一個包含meta信息的文檔。文檔數據本身需要解析并轉換至模型對象來顯示,meta信息也需要解析和轉換。但是,文檔數 據和meta信息不需要交互。我們可以為文檔和meta各創建一個dispatch queue,然后并發執行。文檔和meta的解析代碼都會各自串行執行,從而不用考慮線程安全(只要沒有文檔和meta之間共享的數據),但是它們還是并 發執行的。

一旦文檔打開了,程序需要響應用戶操作。例如,可能需要進行拼寫檢查、代碼高亮、字數統計、自動保存或者其他什么。如果每個任務都被實現為在不同的 dispatch queue中執行,那么這些任務會并發執行,并各自將其他任務的運算考慮在內(respect to each other),從而省去了多線程編程的麻煩。

使用dispatch source(下次我會講到),我們可以讓GCD將事件直接傳遞給用戶隊列。例如,程序中監視socket連接的代碼可以被置于它自己的dispatch queue中,這樣它會異步執行,并且執行時會將程序其他部分的運算考慮在內。另外,如果使用用戶隊列的話,這個模塊會串行執行,簡化程序。

結論

我們討論了如何使用GCD來提升程序性能以及發揮多核系統的優勢。盡管我們需要比較謹慎地編寫并發程序,GCD還是使得我們能更簡單地發揮系統的可用計算資源。

下一篇中,我們將討論dispatch source,也就是GCD的監視內部、外部事件的機制。

GCD介紹(三): Dispatch Sources

何為Dispatch Sources

簡單來說,dispatch source是一個監視某些類型事件的對象。當這些事件發生時,它自動將一個block放入一個dispatch queue的執行例程中。

說的貌似有點不清不楚。我們到底討論哪些事件類型?

下面是GCD 10.6.0版本支持的事件:

Mach port send right state changes.Mach port receive right state changes.External process state change.File descriptor ready for read.File descriptor ready for write.Filesystem node event.POSIX signal.Custom timer.Custom event.

這是一堆很有用的東西,它支持所有kqueue所支持的事件(kqueue是什么?見http://en.wikipedia.org/wiki/Kqueue)以及mach(mach是什么?見http://en.wikipedia.org/wiki/Mach_(kernel))端口、內建計時器支持(這樣我們就不用使用超時參數來創建自己的計時器)和用戶事件。

 

用戶事件

這些事件里面多數都可以從名字中看出含義,但是你可能想知道啥叫用戶事件。簡單地說,這種事件是由你調用dispatch_source_merge_data函數來向自己發出的信號。

這個名字對于一個發出事件信號的函數來說,太怪異了。這個名字的來由是GCD會在事件句柄被執行之前自動將多個事件進行聯結。你可以將數據“拼接” 至dispatch source中任意次,并且如果dispatch queue在這期間繁忙的話,GCD只會調用該句柄一次(不要覺得這樣會有問題,看完下面的內容你就明白了)。

用戶事件有兩種: DISPATCH_SOURCE_TYPE_DATA_ADD 和 DISPATCH_SOURCE_TYPE_DATA_OR.用戶事件源有個 unsigned long data屬性,我們將一個 unsigned long傳入 dispatch_source_merge_data。當使用 _ADD版本時,事件在聯結時會把這些數字相加。當使用 _OR版本時,事件在聯結時會把這些數字邏輯與運算。當事件句柄執行時,我們可以使用dispatch_source_get_data函數訪問當前值,然后這個值會被重置為0。

讓我假設一種情況。假設一些異步執行的代碼會更新一個進度條。因為主線程只不過是GCD的另一個dispatch queue而已,所以我們可以將GUI更新工作push到主線程中。然而,這些事件可能會有一大堆,我們不想對GUI進行頻繁而累贅的更新,理想的情況是 當主線程繁忙時將所有的改變聯結起來。

用dispatch source就完美了,使用DISPATCH_SOURCE_TYPE_DATA_ADD,我們可以將工作拼接起來,然后主線程可以知道從上一次處理完事件到現在一共發生了多少改變,然后將這一整段改變一次更新至進度條。

啥也不說了,上代碼:

    dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());

    dispatch_source_set_event_handler(source, ^{

        [progressIndicator incrementBy:dispatch_source_get_data(source)];

    });

    dispatch_resume(source);

    dispatch_apply([array count], globalQueue, ^(size_t index) {

        // do some work on data at index

        dispatch_source_merge_data(source, 1);

    });

 (對于這段代碼,我很想說點什么,我第一次用dispatch source時,我糾結了很久,很是崩潰:Dispatch queue啟動時默認狀態是掛起的,我們創建完畢之后得主動恢復,否則事件不會被傳送)

假設你已經將進度條的min/max值設置好了,那么這段代碼就完美了。數據會被并發處理。當每一段數據完成后,會通知dispatch source并將dispatch source data加1,這樣我們就認為一個單元的工作完成了。事件句柄根據已完成的工作單元來更新進度條。若主線程比較空閑并且這些工作單元進行的比較慢,那么事 件句柄會在每個工作單元完成的時候被調用,實時更新。如果主線程忙于其他工作,或者工作單元完成速度很快,那么完成事件會被聯結起來,導致進度條只在主線 程變得可用時才被更新,并且一次將積累的改變更新至GUI。

現在你可能會想,聽起來倒是不錯,但是要是我不想讓事件被聯結呢?有時候你可能想讓每一次信號都會引起響應,什么后臺的智能玩意兒統統不要。啊。。 其實很簡單的,把你的思想放到禁錮的框子之外就行了。如果你想讓每一個信號都得到響應,那使用dispatch_async函數不就行了。實際上,使用的 dispatch source而不使用dispatch_async的唯一原因就是利用聯結的優勢。

內建事件

上面就是怎樣使用用戶事件,那么內建事件呢?看看下面這個例子,用GCD讀取標準輸入:

    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    dispatch_source_t stdinSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ,

                                                           STDIN_FILENO,

                                                           0,

                                                           globalQueue);

    dispatch_source_set_event_handler(stdinSource, ^{

        char buf[1024];

        int len = read(STDIN_FILENO, buf, sizeof(buf));

        if(len > 0)

            NSLog(@"Got data from stdin: %.*s", len, buf);

    });

    dispatch_resume(stdinSource);

 簡單的要死!因為我們使用的是全局隊列,句柄自動在后臺執行,與程序的其他部分并行,這意味著對這種情況的提速:事件進入程序時,程序正在處理其他事務。

這是標準的UNIX方式來處理事務的好處,不用去寫loop。如果使用經典的 read調用,我們還得萬分留神,因為返回的數據可能比請求的少,還得忍受無厘頭的“errors”,比如 EINTR (終端系統調用)。使用GCD,我們啥都不用管,就從這些蛋疼的情況里解脫了。如果我們在文件描述符中留下了未讀取的數據,GCD會再次調用我們的句柄。

對于標準輸入,這沒什么問題,但是對于其他文件描述符,我們必須考慮在完成讀寫之后怎樣清除描述符。對于dispatch source還處于活躍狀態時,我們決不能關閉描述符。如果另一個文件描述符被創建了(可能是另一個線程創建的)并且新的描述符剛好被分配了相同的數字, 那么你的dispatch source可能會在不應該的時候突然進入讀寫狀態。de這個bug可不是什么好玩的事兒。

適當的清除方式是使用 dispatch_source_set_cancel_handler,并傳入一個block來關閉文件描述符。然后我們使用 dispatch_source_cancel來取消dispatch source,使得句柄被調用,然后文件描述符被關閉。

使用其他dispatch source類型也差不多。總的來說,你提供一個source(mach port、文件描述符、進程ID等等)的區分符來作為diapatch source的句柄。mask參數通常不會被使用,但是對于 DISPATCH_SOURCE_TYPE_PROC 來說mask指的是我們想要接受哪一種進程事件。然后我們提供一個句柄,然后恢復這個source(前面我加粗字體所說的,得先恢復),搞定。dispatch source也提供一個特定于source的data,我們使用 dispatch_source_get_data函數來訪問它。例如,文件描述符會給出大致可用的字節數。進程source會給出上次調用之后發生的事件的mask。具體每種source給出的data的含義,看man page吧。

計時器

計時器事件稍有不同。它們不使用handle/mask參數,計時器事件使用另外一個函數 dispatch_source_set_timer 來配置計時器。這個函數使用三個參數來控制計時器觸發:

 start參數控制計時器第一次觸發的時刻。參數類型是 dispatch_time_t,這是一個opaque類型,我們不能直接操作它。我們得需要 dispatch_time 和  dispatch_walltime 函數來創建它們。另外,常量  DISPATCH_TIME_NOW 和DISPATCH_TIME_FOREVER 通常很有用。

 interval參數沒什么好解釋的。

 leeway參數比較有意思。這個參數告訴系統我們需要計時器觸發的精準程度。所有的計時器都不會保證100%精準, 這個參數用來告訴系統你希望系統保證精準的努力程度。如果你希望一個計時器沒五秒觸發一次,并且越準越好,那么你傳遞0為參數。另外,如果是一個周期性任 務,比如檢查email,那么你會希望每十分鐘檢查一次,但是不用那么精準。所以你可以傳入60,告訴系統60秒的誤差是可接受的。

這樣有什么意義呢?簡單來說,就是降低資源消耗。如果系統可以讓cpu休息足夠長的時間,并在每次醒來的時候執行一個任務集合,而不是不斷的醒來睡 去以執行任務,那么系統會更高效。如果傳入一個比較大的leeway給你的計時器,意味著你允許系統拖延你的計時器來將計時器任務與其他任務聯合起來一起 執行。

總結

現在你知道怎樣使用GCD的dispatch source功能來監視文件描述符、計時器、聯結的用戶事件以及其他類似的行為。由于dispatch source完全與dispatch queue相集成,所以你可以使用任意的dispatch queue。你可以將一個dispatch source的句柄在主線程中執行、在全局隊列中并發執行、或者在用戶隊列中串行執行(執行時會將程序的其他模塊的運算考慮在內)。

下一篇我會討論如何對dispatch queue進行掛起、恢復、重定目標操作;如何使用dispatch semaphore;如何使用GCD的一次性初始化功能。

GCD介紹(四): 完結

Dispatch Queue掛起

dispatch queue可以被掛起和恢復。使用 dispatch_suspend函數來掛起,使用  dispatch_resume 函數來恢復。這兩個函數的行為是如你所愿的。另外,這兩個還是也可以用于dispatch source。

一個要注意的地方是,dispatch queue的掛起是block粒度的。換句話說,掛起一個queue并不會將當前正在執行的block掛起。它會允許當前執行的block執行完畢,然后后續的block不再會被執行,直至queue被恢復。

還有一個注意點:從man頁上得來的:如果你掛起了一個queue或者source,那么銷毀它之前,必須先對其進行恢復。

Dispatch Queue目標指定

所有的用戶隊列都有一個目標隊列概念。從本質上講,一個用戶隊列實際上是不執行任何任務的,但是它會將任務傳遞給它的目標隊列來執行。通常,目標隊列是默認優先級的全局隊列。

用戶隊列的目標隊列可以用函數 dispatch_set_target_queue來修改。我們可以將任意 dispatch queue傳遞給這個函數,甚至可以是另一個用戶隊列,只要別構成循環就行。這個函數可以用來設定用戶隊列的優先級。比如我們可以將用戶隊列的目標隊列設 定為低優先級的全局隊列,那么我們的用戶隊列中的任務都會以低優先級執行。高優先級也是一樣道理。

有一個用途,是將用戶隊列的目標定為main queue。這會導致所有提交到該用戶隊列的block在主線程中執行。這樣做來替代直接在主線程中執行代碼的好處在于,我們的用戶隊列可以單獨地被掛起 和恢復,還可以被重定目標至一個全局隊列,然后所有的block會變成在全局隊列上執行(只要你確保你的代碼離開主線程不會有問題)。

還有一個用途,是將一個用戶隊列的目標隊列指定為另一個用戶隊列。這樣做可以強制多個隊列相互協調地串行執行,這樣足以構建一組隊列,通過掛起和暫 停那個目標隊列,我們可以掛起和暫停整個組。想象這樣一個程序:它掃描一組目錄并且加載目錄中的內容。為了避免磁盤競爭,我們要確定在同一個物理磁盤上同 時只有一個文件加載任務在執行。而希望可以同時從不同的物理磁盤上讀取多個文件。要實現這個,我們要做的就是創建一個dispatch queue結構,該結構為磁盤結構的鏡像。

首先,我們會掃描系統并找到各個磁盤,為每個磁盤創建一個用戶隊列。然后掃描文件系統,并為每個文件系統創建一個用戶隊列,將這些用戶隊列的目標隊 列指向合適的磁盤用戶隊列。最后,每個目錄掃描器有自己的隊列,其目標隊列指向目錄所在的文件系統的隊列。目錄掃描器枚舉自己的目錄并為每個文件向自己的 隊列提交一個block。由于整個系統的建立方式,就使得每個物理磁盤被串行訪問,而多個物理磁盤被并行訪問。除了隊列初始化過程,我們根本不需要手動干 預什么東西。

信號量

dispatch的信號量是像其他的信號量一樣的,如果你熟悉其他多線程系統中的信號量,那么這一節的東西再好理解不過了。

信號量是一個整形值并且具有一個初始計數值,并且支持兩個操作:信號通知和等待。當一個信號量被信號通知,其計數會被增加。當一個線程在一個信號量上等待時,線程會被阻塞(如果有必要的話),直至計數器大于零,然后線程會減少這個計數。

我們使用函數  dispatch_semaphore_create 來創建dispatch信號量,使用函數  dispatch_semaphore_signal 來信號通知,使用函數 dispatch_semaphore_wait 來等待。這些函數的man頁有兩個很好的例子,展示了怎樣使用信號量來同步任務和有限資源訪問控制。

單次初始化

GCD還提供單詞初始化支持,這個與pthread中的函數  pthread_once 很相似。GCD提供的方式的優點在于它使用block而非函數指針,這就允許更自然的代碼方式:

這個特性的主要用途是惰性單例初始化或者其他的線程安全數據共享。典型的單例初始化技術看起來像這樣(線程安全的):

    + (id)sharedWhatever

    {

        static Whatever *whatever = nil;

        @synchronized([Whatever class])

        {

            if(!whatever)

                whatever = [[Whatever alloc] init];

        }

        return whatever;

    }

這挺好的,但是代價比較昂貴;每次調用  +sharedWhatever 函數都會付出取鎖的代價,即使這個鎖只需要進行一次。確實有更風騷的方式來實現這個,使用類似雙向鎖或者是原子操作的東西,但是這樣挺難弄而且容易出錯。

使用GCD,我們可以這樣重寫上面的方法,使用函數 dispatch_once:

    + (id)sharedWhatever

    {

        static dispatch_once_t pred;

        static Whatever *whatever = nil;

        dispatch_once(&pred, ^{

            whatever = [[Whatever alloc] init];

        });

        return whatever;

    }

這個稍微比 @synchronized方法簡單些,并且GCD確保以更快的方式完成這些檢測,它保證block中的代碼在任何線程通過  dispatch_once 調用之前被執行,但它不會強制每次調用這個函數都讓代碼進行同步控制。實際上,如果你去看這個函數所在的頭文件,你會發現目前它的實現其實是一個宏,進行了內聯的初始化測試,這意味著通常情況下,你不用付出函數調用的負載代價,并且會有更少的同步控制負載。

結論

這一章,我們介紹了dispatch queue的掛起、恢復和目標重定,以及這些功能的一些用途。另外,我們還介紹了如何使用dispatch 信號量和單次初始化功能。到此,我已經完成了GCD如何運作以及如何使用的介紹。


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 若羌县| 曲靖市| 望奎县| 鄂州市| 兴义市| 治多县| 睢宁县| 格尔木市| 泸定县| 武乡县| 潢川县| 资中县| 德令哈市| 本溪| 灵台县| 连平县| 朝阳市| 温泉县| 宁乡县| 白山市| 绵竹市| 双鸭山市| 涪陵区| 恭城| 南靖县| 五指山市| 巩留县| 凤山市| 酒泉市| 清镇市| 开江县| 洞口县| 青岛市| 土默特左旗| 东至县| 新疆| 东光县| 剑川县| 胶州市| 龙南县| 武夷山市|