天天看點

IOS多線程及隊列的使用

最近搞一款塔防遊戲,提到塔防,自然就想到了a星尋路。的确,它是一種高效的尋路算法。但當很多怪物同時在調用a星算法來尋找一條最近的路徑來到達目的地時,我發現會很卡。我都不能接受這個卡屏,更何況是玩家呢。

所有我一直都在努力去優化a星算法。雖然有所改善,但卡的問題還是存在。

實在沒轍了,我想到了隊列線程。之前都沒接觸過這個東東,還好在網上找到很詳細的線程介紹。當然,我隻是用到了其中的一點點。分享給大家,希望有所幫助。

目錄:

ios多線程程式設計之nsthread的使用

ios多線程程式設計之nsoperation和nsoperationqueue的使用

ios多線程程式設計之grand central dispatch(gcd)介紹和使用

1、簡介:

1.1 ios有三種多線程程式設計的技術,分别是:

1.、nsthread

2、cocoa nsoperation (ios多線程程式設計之nsoperation和nsoperationqueue的使用)

3、gcd 全稱:grand central dispatch( ios多線程程式設計之grand central dispatch(gcd)介紹和使用)

這三種程式設計方式從上到下,抽象度層次是從低到高的,抽象度越高的使用越簡單,也是apple最推薦使用的。

這篇我們主要介紹和使用nsthread,後面會繼續2、3 的講解和使用。

1.2 三種方式的有缺點介紹:

nsthread:

優點:nsthread 比其他兩個輕量級

缺點:需要自己管理線程的生命周期,線程同步。線程同步對資料的加鎖會有一定的系統開銷

nsthread實作的技術有下面三種:

technology

description

cocoa threads

cocoa implements threads using the nsthread class. cocoa also provides methods on nsobject for spawning new threads and executing code on already-running threads. for more information, see “using nsthread” and “using nsobject to spawn a thread.”

posix threads

posix threads provide a c-based interface for creating threads. if you are not writing a cocoa application, this is the best choice for creating threads. the posix interface is relatively simple to use and offers ample

flexibility for configuring your threads. for more information, see “using posix threads”

multiprocessing services

multiprocessing services is a legacy c-based interface used by applications transitioning from older versions of mac os. this technology is available in os x only and should be avoided for any new development. instead, you should use the nsthread class

or posix threads. if you need more information on this technology, see multiprocessing services programming guide.

一般使用cocoa thread 技術。

cocoa operation

優點:不需要關心線程管理,資料同步的事情,可以把精力放在自己需要執行的操作上。

cocoa operation 相關的類是 nsoperation ,nsoperationqueue。nsoperation是個抽象類,使用它必須用它的子類,可以實作它或者使用它定義好的兩個子類:nsinvocationoperation 和 nsblockoperation。建立nsoperation子類的對象,把對象添加到nsoperationqueue隊列裡執行。

gcd

grand central dispatch (gcd)是apple開發的一個多核程式設計的解決方法。在ios4.0開始之後才能使用。gcd是一個替代諸如nsthread, nsoperationqueue, nsinvocationoperation等技術的很高效和強大的技術。現在的ios系統都更新到6了,是以不用擔心該技術不能使用。

介紹完這三種多線程程式設計方式,我們這篇先介紹nsthread的使用。

2、nsthread的使用

2.1 nsthread 有兩種直接建立方式:

- (id)initwithtarget:(id)target selector:(sel)selector object:(id)argument

+ (void)detachnewthreadselector:(sel)aselector totarget:(id)atarget withobject:(id)anargument

第一個是執行個體方法,第二個是類方法

複制代碼

[nsthread detachnewthreadselector:@selector(dosomething:) totarget:self withobject:nil];

nsthread* mythread = [[nsthread alloc] initwithtarget:self                                        selector:@selector(dosomething:)                                        object:nil]; [mythread start];

2.2參數的意義:

selector :線程執行的方法,這個selector隻能有一個參數,而且不能有傳回值。

target :selector消息發送的對象

argument:傳輸給target的唯一參數,也可以是nil

第一種方式會直接建立線程并且開始運作線程,第二種方式是先建立線程對象,然後再運作線程操作,在運作線程操作前可以設定線程的優先級等線程資訊

2.3 ps:不顯式建立線程的方法:

用nsobject的類方法 performselectorinbackground:withobject: 建立一個線程:

[obj performselectorinbackground:@selector(dosomething) withobject:nil];

2.4 下載下傳圖檔的例子:

2.4.1 建立singeview app

建立項目,并在xib檔案上放置一個imageview控件。按住control鍵拖到viewcontroll

er.h檔案中建立imageview iboutlet

viewcontroller.m中實作:

//

//  viewcontroller.m

//  nsthreaddemo

//  created by rongfzh on 12-9-23.

//  copyright (c) 2012年 rongfzh. all rights reserved.

#import "viewcontroller.h"

#define kurl @"http://avatar.csdn.net/2/c/d/1_totogo2010.jpg"

@interface viewcontroller ()

@end

@implementation viewcontroller

-(void)downloadimage:(nsstring *) url{

    nsdata *data = [[nsdata alloc] initwithcontentsofurl:[nsurl urlwithstring:url]];

    uiimage *image = [[uiimage alloc]initwithdata:data];

    if(image == nil){

    }else{

        [self performselectoronmainthread:@selector(updateui:) withobject:image waituntildone:yes];

    }

}

-(void)updateui:(uiimage*) image{

    self.imageview.image = image;

- (void)viewdidload

{

    [super viewdidload];

//    [nsthread detachnewthreadselector:@selector(downloadimage:) totarget:self withobject:kurl];

    nsthread *thread = [[nsthread alloc]initwithtarget:self selector:@selector(downloadimage:) object:kurl];

    [thread start];

- (void)didreceivememorywarning

    [super didreceivememorywarning];

    // dispose of any resources that can be recreated.

2.4.2線程間通訊

線程下載下傳完圖檔後怎麼通知主線程更新界面呢?

[self performselectoronmainthread:@selector(updateui:) withobject:image waituntildone:yes];

performselectoronmainthread是nsobject的方法,除了可以更新主線程的資料外,還可以更新其他線程的比如:

用:performselector:onthread:withobject:waituntildone:

2.3 線程同步

我們示範一個經典的賣票的例子來講nsthread的線程同步:

.h

#import <uikit/uikit.h>

@class viewcontroller;

@interface appdelegate : uiresponder <uiapplicationdelegate>

    int tickets;

    int count;

    nsthread* ticketsthreadone;

    nsthread* ticketsthreadtwo;

    nscondition* ticketscondition;

    nslock *thelock;

@property (strong, nonatomic) uiwindow *window;

@property (strong, nonatomic) viewcontroller *viewcontroller;

- (bool)application:(uiapplication *)application didfinishlaunchingwithoptions:(nsdictionary *)launchoptions

    tickets = 100;

    count = 0;

    thelock = [[nslock alloc] init];

    // 鎖對象

    ticketscondition = [[nscondition alloc] init];

    ticketsthreadone = [[nsthread alloc] initwithtarget:self selector:@selector(run) object:nil];

    [ticketsthreadone setname:@"thread-1"];

    [ticketsthreadone start];

    ticketsthreadtwo = [[nsthread alloc] initwithtarget:self selector:@selector(run) object:nil];

    [ticketsthreadtwo setname:@"thread-2"];

    [ticketsthreadtwo start];

    self.window = [[uiwindow alloc] initwithframe:[[uiscreen mainscreen] bounds]];

    // override point for customization after application launch.

    self.viewcontroller = [[viewcontroller alloc] initwithnibname:@"viewcontroller" bundle:nil];

    self.window.rootviewcontroller = self.viewcontroller;

    [self.window makekeyandvisible];

    return yes;

- (void)run{

    while (true) {

        // 上鎖

//        [ticketscondition lock];

        [thelock lock];

        if(tickets >= 0){

            [nsthread sleepfortimeinterval:0.09];

            count = 100 - tickets;

            nslog(@"目前票數是:%d,售出:%d,線程名:%@",tickets,count,[[nsthread currentthread] name]);

            tickets--;

        }else{

            break;

        }

        [thelock unlock];

//        [ticketscondition unlock];

如果沒有線程同步的lock,賣票數可能是-1.加上lock之後線程同步保證了資料的正确性。

上面例子我使用了兩種鎖,一種nscondition ,一種是:nslock。 nscondition我已經注釋了。

線程的順序執行

他們都可以通過

[ticketscondition signal]; 發送信号的方式,在一個線程喚醒另外一個線程的等待。

比如:

#import "appdelegate.h"

@implementation appdelegate

    nsthread *ticketsthreadthree = [[nsthread alloc] initwithtarget:self selector:@selector(run3) object:nil];

    [ticketsthreadthree setname:@"thread-3"];

    [ticketsthreadthree start];    

-(void)run3{

    while (yes) {

        [ticketscondition lock];

        [nsthread sleepfortimeinterval:3];

        [ticketscondition signal];

        [ticketscondition unlock];

        [ticketscondition wait];

wait是等待,我加了一個 線程3 去喚醒其他兩個線程鎖中的wait

其他同步

我們可以使用指令 @synchronized 來簡化 nslock的使用,這樣我們就不必顯示編寫建立nslock,加鎖并解鎖相關代碼。

- (void)dosomething:(id)anobj

@synchronized(anobj)

// everything between the braces is protected by the @synchronized directive.

前一篇 ios多線程程式設計之nsthread的使用介紹三種多線程程式設計和nsthread的使用,這篇介紹nsoperation的使用。

使用 nsoperation的方式有兩種,

一種是用定義好的兩個子類:nsinvocationoperation 和 nsblockoperation。

另一種是繼承nsoperation

如果你也熟悉java,nsoperation就和java.lang.runnable接口很相似。和java的runnable一樣,nsoperation也是設計用來擴充的,隻需繼承重寫nsoperation的一個方法main。相當與java 中runnalbe的run方法。然後把nsoperation子類的對象放入nsoperationqueue隊列中,該隊列就會啟動并開始處理它

nsinvocationoperation例子:

和前面一篇博文一樣,我們實作一個下載下傳圖檔的例子。建立一個single view app,拖放一個imageview控件到xib界面。

實作代碼如下:

#define kurl @"http://avatar.csdn.net/2/c/d/1_totogo2010.jpg"@interface viewcontroller ()@end@implementation viewcontroller- (void)viewdidload{    [super viewdidload];    nsinvocationoperation *operation = [[nsinvocationoperation alloc]initwithtarget:self                                                                          

selector:@selector(downloadimage:)                                                                             object:kurl];        nsoperationqueue *queue = [[nsoperationqueue alloc]init];    [queue addoperation:operation];    // do any additional setup after

loading the view, typically from a nib.}-(void)downloadimage:(nsstring *)url{    nslog(@"url:%@", url);    nsurl *nsurl = [nsurl urlwithstring:url];    nsdata *data = [[nsdata alloc]initwithcontentsofurl:nsurl];    uiimage * image = [[uiimage alloc]initwithdata:data];    [self

performselectoronmainthread:@selector(updateui:) withobject:image waituntildone:yes];}-(void)updateui:(uiimage*) image{    self.imageview.image = image; }

1.viewdidload方法裡可以看到我們用nsinvocationoperation建了一個背景線程,并且放到nsoperationqueue中。背景線程執行downloadimage方法。

2.downloadimage 方法處理下載下傳圖檔的邏輯。下載下傳完成後用performselectoronmainthread執行主線程updateui方法。

3.updateui 并把下載下傳的圖檔顯示到圖檔控件中。

第二種方式繼承nsoperation

在.m檔案中實作main方法,main方法編寫要執行的代碼即可。

如何控制線程池中的線程數?

隊列裡可以加入很多個nsoperation, 可以把nsoperationqueue看作一個線程池,可往線程池中添加操作(nsoperation)到隊列中。線程池中的線程可看作消費者,從隊列中取走操作,并執行它。

通過下面的代碼設定:

[queue setmaxconcurrentoperationcount:5];線程池中的線程數,也就是并發操作數。預設情況下是-1,-1表示沒有限制,這樣會同時運作隊列中的全部的操作。

介紹:

grand central dispatch 簡稱(gcd)是蘋果公司開發的技術,以優化的應用程式支援多核心處理器和其他的對稱多處理系統的系統。這建立在任務并行執行的線程池模式的基礎上的。它首次釋出在mac os x 10.6 ,ios 4及以上也可用。

設計:

gcd的工作原理是:讓程式平行排隊的特定任務,根據可用的處理資源,安排他們在任何可用的處理器核心上執行任務。

一個任務可以是一個函數(function)或者是一個block。 gcd的底層依然是用線程實作,不過這樣可以讓程式員不用關注實作的細節。

gcd中的fifo隊列稱為dispatch queue,它可以保證先進來的任務先得到執行

dispatch queue分為下面三種:

serial

又稱為private dispatch queues,同時隻執行一個任務。serial queue通常用于同步通路特定的資源或資料。當你建立多個serial queue時,雖然它們各自是同步執行的,但serial queue與serial queue之間是并發執行的。

concurrent

又稱為global dispatch queue,可以并發地執行多個任務,但是執行完成的順序是随機的。

main dispatch queue

它是全局可用的serial queue,它是在應用程式主線程上執行任務的。

我們看看dispatch queue如何使用

1、常用的方法dispatch_async

為了避免界面在處理耗時的操作時卡死,比如讀取網絡資料,io,資料庫讀寫等,我們會在另外一個線程中處理這些操作,然後通知主線程更新界面。

用gcd實作這個流程的操作比前面介紹的nsthread nsoperation的方法都要簡單。代碼架構結構如下:

dispatch_async(dispatch_get_global_queue(dispatch_queue_priority_default, 0), ^{

    // 耗時的操作

    dispatch_async(dispatch_get_main_queue(), ^{

        // 更新界面

    });

});

如果這樣還不清晰的話,那我們還是用上兩篇部落格中的下載下傳圖檔為例子,代碼如下:

  dispatch_async(dispatch_get_global_queue(dispatch_queue_priority_default, 0), ^{

        nsurl * url = [nsurl urlwithstring:@"http://avatar.csdn.net/2/c/d/1_totogo2010.jpg"];

        nsdata * data = [[nsdata alloc]initwithcontentsofurl:url];

        uiimage *image = [[uiimage alloc]initwithdata:data];

        if (data != nil) {

            dispatch_async(dispatch_get_main_queue(), ^{

                self.imageview.image = image;

             });

是不是代碼比nsthread nsoperation簡潔很多,而且gcd會自動根據任務在多核處理器上配置設定資源,優化程式。

系統給每一個應用程式提供了三個concurrent dispatch queues。這三個并發排程隊列是全局的,它們隻有優先級的不同。因為是全局的,我們不需要去建立。我們隻需要通過使用函數dispath_get_global_queue去得到隊列,如下:

dispatch_queue_t globalq = dispatch_get_global_queue(dispatch_queue_priority_default, 0);

這裡也用到了系統預設就有一個串行隊列main_queue

dispatch_queue_t mainq = dispatch_get_main_queue();

雖然dispatch queue是引用計數的對象,但是以上兩個都是全局的隊列,不用retain或release。

2、dispatch_group_async的使用

dispatch_group_async可以實作監聽一組任務是否完成,完成後得到通知執行其他的操作。這個方法很有用,比如你執行三個下載下傳任務,當三個任務都下載下傳完成後你才通知界面說完成的了。下面是一段例子代碼:

dispatch_queue_t queue = dispatch_get_global_queue(dispatch_queue_priority_default, 0);

    dispatch_group_t group = dispatch_group_create();

    dispatch_group_async(group, queue, ^{

        [nsthread sleepfortimeinterval:1];

        nslog(@"group1");

        [nsthread sleepfortimeinterval:2];

        nslog(@"group2");

        nslog(@"group3");

    dispatch_group_notify(group, dispatch_get_main_queue(), ^{

        nslog(@"updateui");

    dispatch_release(group); 

dispatch_group_async是異步的方法,運作後可以看到列印結果:

2012-09-25 16:04:16.737 gcdtest[43328:11303] group1

2012-09-25 16:04:17.738 gcdtest[43328:12a1b] group2

2012-09-25 16:04:18.738 gcdtest[43328:13003] group3

2012-09-25 16:04:18.739 gcdtest[43328:f803] updateui

每個一秒列印一個,當第三個任務執行後,upadteui被列印。

3、dispatch_barrier_async的使用

dispatch_barrier_async是在前面的任務執行結束後它才執行,而且它後面的任務等它執行完成之後才會執行

例子代碼如下:

dispatch_queue_t queue = dispatch_queue_create("gcdtest.rongfzh.yc", dispatch_queue_concurrent);

    dispatch_async(queue, ^{

        nslog(@"dispatch_async1");

        [nsthread sleepfortimeinterval:4];

        nslog(@"dispatch_async2");

    dispatch_barrier_async(queue, ^{

        nslog(@"dispatch_barrier_async");

        nslog(@"dispatch_async3");

列印結果:

2012-09-25 16:20:33.967 gcdtest[45547:11203] dispatch_async1

2012-09-25 16:20:35.967 gcdtest[45547:11303] dispatch_async2

2012-09-25 16:20:35.967 gcdtest[45547:11303] dispatch_barrier_async

2012-09-25 16:20:40.970 gcdtest[45547:11303] dispatch_async3

請注意執行的時間,可以看到執行的順序如上所述。

4、dispatch_apply

執行某個代碼片段n次。

dispatch_apply(5, globalq, ^(size_t index) {

// 執行5次