iOS學習筆記11-多線程入門

一、iOS多線程

iOS多線程開發有三種方式:
  1. NSThread
  2. NSOperation
  3. GCD

iOS在每個進程啓動後都會創建一個主線程更新UI要在主線程上,所以也稱爲UI線程,是其他線程的父線程。

線程和進程的區別傻傻分不清楚:
  • 線程(thread):用於指代獨立執行的代碼段。
  • 進程(process):用於指代一個正在運行的可執行程序,它可以包含多個線程。

多線程加載圖片

二、NSThread

NSThreadhi輕量級的多線程開發,需要自己管理線程生命週期

創建線程主要實現方法:
/* 直接將操作添加到新線程中並執行,該方法無法拿到線程對象 */
+ (void)detachNewThreadSelector:(SEL)selector /* 方法名 */
                       toTarget:(id)target /* 調用對象 */
                     withObject:(id)argument; /* 參數 */
/* 創建線程對象,初始化線程任務,調用start方法啓動線程 */
- (instancetype)initWithTarget:(id)target /* 調用對象 */
                      selector:(SEL)selector /* 方法名 */
                        object:(id)argument;/* 參數 */
實際使用:
/* 創建一個線程,初始化任務,創建線程並不會啓動線程 */
NSThread *thread = [[NSThread alloc] initWithTarget:self 
                                           selector:@selector(loadImage) 
                                             object:nil];
[thread start];//啓動線程

/* 直接將操作添加到新線程並啓動線程 */
[NSThread detachNewThreadSelector:@selector(loadImage) 
                         toTarget:self 
                       withObject:nil];
  • 每個線程的實際執行順序並不一定按啓動順序執行
  • 如果是單核CPU,多線程是併發,分時間片切換執行不同線程,多核CPU的多線程纔是真正的並行運算。
線程狀態分爲:
  • isExecuting(正在執行)
  • isFinished(已經完成)
  • isCancellled(已經取消)
下面是常用方法來控制線程:
/* 讓線程休眠 */
+ (void)sleepUntilDate:(NSDate *)date;/* 讓當前執行線程休眠到某個時間 */
+ (void)sleepForTimeInterval:(NSTimeInterval)time;/* 讓當前執行線程休眠固定多少秒 */
/* 終止線程 */
+ (void)exit;
/* 停止線程,注意在主線程中調用僅僅只是設置線程狀態,不會立刻停止線程 */
- (void)cancel;
實例:
NSThread *thread = threads[i];
//判斷線程是否完成,如果沒有完成則設置爲取消狀態
//注意設置爲取消狀態僅僅是改變了線程狀態而言,並不能立刻終止線程
if ( !thread.isFinished ) {
    [thread cancel];
}
//線程休眠2秒
[NSThread sleepForTimeInterval:2.0];

我們知道了控制單個線程,怎麼在線程之間進行通信呢?

下面是線程間通信的常用方法:
/* 在後臺執行一個操作,本質就是重新創建一個線程執行當前方法 */
- (void)performSelectorInBackground:(SEL)aSelector
                         withObject:(id)arg;
/* 在指定的線程上執行一個方法,需要用戶創建一個線程對象 */
- (void)performSelector:(SEL)aSelector
               onThread:(NSThread *)thr
             withObject:(id)arg
          waitUntilDone:(BOOL)wait;
/* 在主線程上執行一個方法 */
- (void)performSelectorOnMainThread:(SEL)aSelector
                         withObject:(id)arg
                      waitUntilDone:(BOOL)wait;

三、NSOperation

只要將NSOperation放入NSOperationQueue線程隊列中,就會啓動執行。
NSOperationQueue負責管理、執行所有的NSOperation,這樣更加容易管理線程總數控制線程之間的依賴

NSOperation是個基類,我們直接使用的是它的子類:
  • NSInvocationOperation:調用方法SEL的方式執行線程可以使用它
  • NSBlockOperation:調用Block的方式執行線程可以使用它
下面是使用實例:
//創建Invocation線程
NSInvocationOperation *invocationOperation = 
        [[NSInvocationOperation alloc] initWithTarget:self 
                                             selector:@selector(loadImage) 
                                               object:nil];
//注意如果直接調用start方法,則此操作會在主線程中調用
// [invocationOperation start];//一般不會這麼操作,而是添加到NSOperationQueue中
//創建線程隊列
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
operationQueue.maxConcurrentOperationCount = 5;//設置最大併發線程數
//注意添加到線程隊列後,隊列裏的線程就會開始執行
[operationQueue addOperation:invocationOperation];

//創建Block線程
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
    [self loadImage:[NSNumber numberWithInt:0]];
}];
//添加進線程隊列
[operationQueue addOperation:blockOperation];

如果覺得添加NSBlockOperation線程麻煩,還有個簡單的方法,是NSOperationQueue的對象方法:

// 快捷添加NSBlockOperation
[operationQueue addOperationWithBlock:^{
    [self loadImage:[NSNumber numberWithInt:0]];
}];

我說過NSOperationQueue可以控制線程之間的依賴,這是怎麼一回事呢?想象一種場景,加載多個圖片,但我想最後一張圖片一定要先加載,其他圖片加載的前提就是最後一張圖片要加載完成,這時候就可以使用依賴了。非常簡單。

多圖片加載實例:
- (void)loadImageWithMultiThread{
    int count = ROW_COUNT*COLUMN_COUNT;
    //創建線程隊列
    NSOperationQueue *operationQueue = [[NSOperationQueue alloc]init];
    operationQueue.maxConcurrentOperationCount = 5;//設置最大併發線程數
    //創建加載最後一張圖片的線程
    NSBlockOperation *lastBlockOperation = [NSBlockOperation blockOperationWithBlock:^{
        [self loadImage:[NSNumber numberWithInt:(count-1)]];
    }];
    //創建多個線程用於下載其他圖片
    for (int i=0; i<count-1; ++i) {
        //創建多線程操作
        NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
            [self loadImage:[NSNumber numberWithInt:i]];
        }];
        //設置依賴操作爲最後一張圖片加載操作,只有最後一張圖片加載完成,其他圖片纔開始陸續加載
        [blockOperation addDependency:lastBlockOperation];
        [operationQueue addOperation:blockOperation];
    }
    //將最後一個圖片的加載線程加入線程隊列
    [operationQueue addOperation:lastBlockOperation];
}

依賴的應用-多圖片加載-最後一個圖片先加載,其他纔開始加載

四、GCD

GCD中也有一個類似於NSOperationQueue的隊列,GCD統一管理整個隊列中的任務,GCD是C語言下的框架。

GCD中的隊列分爲並行隊列和串行隊列:
  • 串行隊列(serial):只有一個線程,加入到隊列中的操作按添加順序依次執行。
  • 併發隊列(concurrent):有多個線程,操作進來之後它會將這些隊列安排在可用的處理器上,同時保證先進來的任務優先處理,但不是順序的。

其實在GCD中還有一個特殊隊列就是主隊列,用來執行主線程上的操作任務。

GCD執行方式也分爲異步執行和同步執行:
  • dispatch_async(異步執行) :不管隊列中的任務執行完還是沒執行完,直接將任務追加到隊列
  • dispatch_sync(同步執行) : 等隊列中的任務執行完,再將任務追加到隊列
串行隊列和併發隊列的創建:
/*創建一個隊列
 第一個參數:隊列名稱
 第二個參數:隊列類型,DISPATCH_QUEUE_SERIAL串行,DISPATCH_QUEUE_CONCURRENT併發
 注意:GCD的queue不是指針類型
*/
dispatch_queue_t serialQueue = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t concurrentQueue = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);

/* 使用dispatch_get_global_queue() 方法取得一個全局的併發隊列 */
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
隊列的異步執行和同步執行:
//異步執行隊列任務,第一個參數是隊列,第二個參數是任務Block
dispatch_async(queue, ^{
    [self loadImage:[NSNumber numberWithInt:i]];
}); 
//同步執行隊列任務,第一個參數是隊列,第二個參數是任務Block
dispatch_sync(queue, ^{
    [self loadImage:[NSNumber numberWithInt:i]];
}); 
  • 在GDC中一個操作是多線程執行還是單線程執行,取決於當前隊列類型和執行方法,只有隊列類型爲並行隊列並且使用異步方法執行時才能在多個線程中併發執行
  • 串行隊列可以按順序執行,並行隊列的異步方法無法確定執行順序。
  • 更新UI界面最好採用同步方法,其他操作採用異步方法。
GCD的其他任務執行方法:
/* 重複執行某個任務,爲了不阻塞線程可以使用dispatch_async()包裝一下再執行 */
dispatch_apply(size_t iterations, dispatch_queue_t queue, void (^block)(size_t));

/* 單次執行一個任務,此方法中的任務只會執行一次,重複調用也沒辦法重複執行(單例模式中常用此方法)*/
dispatch_once(dispatch_once_t *predicate, dispatch_block_t block);

/* 常用:延遲delayInSeconds秒後在隊列queue執行block操作 */
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)), dispatch_queue_t queue, dispatch_block_t block);

五、線程同步

爲什麼需要線程同步?因爲要解決多線程的資源搶奪的問題

1.NSLock同步鎖

//初始化鎖對象
NSLock *myLock = [[NSLock alloc] init];
//加鎖
[myLock lock];//加鎖後,下面的代碼只能有一個線程進入執行
if (_imageNames.count > 0) {
    name = [_imageNames lastObject];
    [_imageNames removeObject:name];
}
//使用完解鎖
[myLock unlock];

2.@synchronized代碼塊

//線程同步
@synchronized(self){
    if (_imageNames.count > 0) {
        name = [_imageNames lastObject];
        [_imageNames removeObject:name];
    }
}

線程同步就不細講了,這是一個大塊知識。

如果你喜歡我的文章,請關注我O(∩_∩)O哈~
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章