多線程

https://www.cnblogs.com/jx66/p/5849741.html

大家都知道焚刚,在開發(fā)過程中應(yīng)該盡可能減少用戶等待時(shí)間点弯,讓程序盡可能快的完成運(yùn)算】蠊荆可是無論是哪種語言開發(fā)的程序最終往往轉(zhuǎn)換成匯編語言進(jìn)而解釋成機(jī)器碼來執(zhí)行抢肛。但是機(jī)器碼是按順序執(zhí)行的,一個復(fù)雜的多步操作只能一步步按順序逐個執(zhí)行碳柱。改變這種狀況可以從兩個角度出發(fā):對于單核處理器捡絮,可以將多個步驟放到不同的線程,這樣一來用戶完成UI操作后其他后續(xù)任務(wù)在其他線程中莲镣,當(dāng)CPU空閑時(shí)會繼續(xù)執(zhí)行福稳,而此時(shí)對于用戶而言可以繼續(xù)進(jìn)行其他操作;對于多核處理器剥悟,如果用戶在UI線程中完成某個操作之后灵寺,其他后續(xù)操作在別的線程中繼續(xù)執(zhí)行,用戶同樣可以繼續(xù)進(jìn)行其他UI操作区岗,與此同時(shí)前一個操作的后續(xù)任務(wù)可以分散到多個空閑CPU中繼續(xù)執(zhí)行(當(dāng)然具體調(diào)度順序要根據(jù)程序設(shè)計(jì)而定)略板,及解決了線程阻塞又提高了運(yùn)行效率。蘋果從iPad2 開始使用雙核A5處理器(iPhone中從iPhone 4S開始使用)慈缔,A7中還加入了協(xié)處理器叮称,如何充分發(fā)揮這些處理器的性能確實(shí)值得思考。今天將重點(diǎn)分析iOS多線程開發(fā):

  1. 多線程
    1. 簡介
    2. iOS多線程
  2. NSThread
    1. 解決線程阻塞問題
    2. 多線程并發(fā)
    3. 線程狀態(tài)
    4. 擴(kuò)展-NSObject分類擴(kuò)展
  3. NSOperation
    1. NSInvocationOperation
    2. NSBlockOperation
    3. 線程執(zhí)行順序
  4. GCD
    1. 串行隊(duì)列
    2. 并發(fā)隊(duì)列
    3. 其他任務(wù)執(zhí)行方法
  5. 線程同步
    1. NSLock同步鎖
    2. @synchronized代碼塊
    3. 擴(kuò)展--使用GCD解決資源搶占問題
    4. 擴(kuò)展--控制線程通信
  6. 總結(jié)
  7. 目 錄

<a name="t1"></a>多線程

<a name="t2"></a>簡介

當(dāng)用戶播放音頻、下載資源瓤檐、進(jìn)行圖像處理時(shí)往往希望做這些事情的時(shí)候其他操作不會被中斷或者希望這些操作過程中更加順暢赂韵。在單線程中一個線程只能做一件事情,一件事情處理不完另一件事就不能開始挠蛉,這樣勢必影響用戶體驗(yàn)祭示。早在單核處理器時(shí)期就有多線程,這個時(shí)候多線程更多的用于解決線程阻塞造成的用戶等待(通常是操作完UI后用戶不再干涉谴古,其他線程在等待隊(duì)列中质涛,CPU一旦空閑就繼續(xù)執(zhí)行,不影響用戶其他UI操作)掰担,其處理能力并沒有明顯的變化汇陆。如今無論是移動操作系統(tǒng)還是PC、服務(wù)器都是多核處理器带饱,于是“并行運(yùn)算”就更多的被提及毡代。一件事情我們可以分成多個步驟,在沒有順序要求的情況下使用多線程既能解決線程阻塞又能充分利用多核處理器運(yùn)行能力勺疼。

下圖反映了一個包含8個操作的任務(wù)在一個有兩核心的CPU中創(chuàng)建四個線程運(yùn)行的情況教寂。假設(shè)每個核心有兩個線程,那么每個CPU中兩個線程會交替執(zhí)行恢口,兩個CPU之間的操作會并行運(yùn)算孝宗。單就一個CPU而言兩個線程可以解決線程阻塞造成的不流暢問題,其本身運(yùn)行效率并沒有提高耕肩,多CPU的并行運(yùn)算才真正解決了運(yùn)行效率問題因妇,這也正是并發(fā)和并行的區(qū)別。當(dāng)然猿诸,不管是多核還是單核開發(fā)人員不用過多的擔(dān)心婚被,因?yàn)槿蝿?wù)具體分配給幾個CPU運(yùn)算是由系統(tǒng)調(diào)度的,開發(fā)人員不用過多關(guān)心系統(tǒng)有幾個CPU梳虽。開發(fā)人員需要關(guān)心的是線程之間的依賴關(guān)系址芯,因?yàn)橛行┎僮鞅仨氃谀硞€操作完成完才能執(zhí)行,如果不能保證這個順序勢必會造成程序問題窜觉。

<a name="t3"></a>iOS多線程

在iOS中每個進(jìn)程啟動后都會建立一個主線程(UI線程)谷炸,這個線程是其他線程的父線程。由于在iOS中除了主線程禀挫,其他子線程是獨(dú)立于Cocoa Touch的旬陡,所以只有主線程可以更新UI界面(新版iOS中,使用其他線程更新UI可能也能成功语婴,但是不推薦)描孟。iOS中多線程使用并不復(fù)雜驶睦,關(guān)鍵是如何控制好各個線程的執(zhí)行順序、處理好資源競爭問題匿醒。常用的多線程開發(fā)有三種方式:

1.NSThread

2.NSOperation

3.GCD

三種方式是隨著iOS的發(fā)展逐漸引入的场航,所以相比而言后者比前者更加簡單易用,并且GCD也是目前蘋果官方比較推薦的方式(它充分利用了多核處理器的運(yùn)算性能)廉羔。做過.Net開發(fā)的朋友不難發(fā)現(xiàn)其實(shí)這三種開發(fā)方式 剛好對應(yīng).Net中的多線程溉痢、線程池和異步調(diào)用,因此在文章中也會對比講解蜜另。

<a name="t4"></a>NSThread

NSThread是輕量級的多線程開發(fā)适室,使用起來也并不復(fù)雜,但是使用NSThread需要自己管理線程生命周期举瑰。可以使用對象方法+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument直接將操作添加到線程中并啟動蔬螟,也可以使用對象方法**- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(id)argument **創(chuàng)建一個線程對象此迅,然后調(diào)用start方法啟動線程。

<a name="t5"></a>解決線程阻塞問題

在資源下載過程中旧巾,由于網(wǎng)絡(luò)原因有時(shí)候很難保證下載時(shí)間耸序,如果不使用多線程可能用戶完成一個下載操作需要長時(shí)間的等待,這個過程中無法進(jìn)行其他操作鲁猩。下面演示一個采用多線程下載圖片的過程坎怪,在這個示例中點(diǎn)擊按鈕會啟動一個線程去下載圖片,下載完成后使用UIImageView將圖片顯示到界面中廓握〗亮可以看到用戶點(diǎn)擊完下載按鈕后,不管圖片是否下載完成都可以繼續(xù)操作界面隙券,不會造成阻塞男应。

<pre name="code" class="code" style="margin: 0px; white-space: pre-wrap; word-wrap: break-word; padding: 0px 10px; list-style-type: none; list-style-image: none; font-family: 'Courier New', Courier, monospace;">//
// NSThread實(shí)現(xiàn)多線程
// MultiThread
//
// Created by Kenshin Cui on 14-3-22.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

import "KCMainViewController.h"

@interface KCMainViewController (){
UIImageView *_imageView;
}

@end

@implementation KCMainViewController

  • (void)viewDidLoad {
    [super viewDidLoad];

    [self layoutUI];
    }

pragma mark 界面布局

-(void)layoutUI{
_imageView =[[UIImageView alloc]initWithFrame:[UIScreen mainScreen].applicationFrame];
_imageView.contentMode=UIViewContentModeScaleAspectFit;
[self.view addSubview:_imageView];

UIButton *button=[UIButton buttonWithType:UIButtonTypeRoundedRect];
button.frame=CGRectMake(50, 500, 220, 25);
[button setTitle:@"加載圖片" forState:UIControlStateNormal];
//添加方法
[button addTarget:self action:@selector(loadImageWithMultiThread) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];

}

pragma mark 將圖片顯示到界面

-(void)updateImage:(NSData *)imageData{
UIImage *image=[UIImage imageWithData:imageData];
_imageView.image=image;
}

pragma mark 請求圖片數(shù)據(jù)

-(NSData *)requestData{
//對于多線程操作建議把線程操作放到@autoreleasepool中
@autoreleasepool {
NSURL *url=[NSURL URLWithString:@"http://images.apple.com/iphone-6/overview/images/biggest_right_large.png"];
NSData *data=[NSData dataWithContentsOfURL:url];
return data;
}
}

pragma mark 加載圖片

-(void)loadImage{
//請求數(shù)據(jù)
NSData data= [self requestData];
/
將數(shù)據(jù)顯示到UI控件,注意只能在主線程中更新UI,
另外performSelectorOnMainThread方法是NSObject的分類方法,每個NSObject對象都有此方法娱仔,
它調(diào)用的selector方法是當(dāng)前調(diào)用控件的方法沐飘,例如使用UIImageView調(diào)用的時(shí)候selector就是UIImageView的方法
Object:代表調(diào)用方法的參數(shù),不過只能傳遞一個參數(shù)(如果有多個參數(shù)請使用對象進(jìn)行封裝)
waitUntilDone:是否線程任務(wù)完成執(zhí)行
*/
[self performSelectorOnMainThread:@selector(updateImage:) withObject:data waitUntilDone:YES];
}

pragma mark 多線程下載圖片

-(void)loadImageWithMultiThread{
//方法1:使用對象方法
//創(chuàng)建一個線程,第一個參數(shù)是請求的操作牲迫,第二個參數(shù)是操作方法的參數(shù)
// NSThread *thread=[[NSThread alloc]initWithTarget:self selector:@selector(loadImage) object:nil];
// //啟動一個線程耐朴,注意啟動一個線程并非就一定立即執(zhí)行,而是處于就緒狀態(tài)盹憎,當(dāng)系統(tǒng)調(diào)度時(shí)才真正執(zhí)行
// [thread start];

//方法2:使用類方法
[NSThread detachNewThreadSelector:@selector(loadImage) toTarget:self withObject:nil];

}
@end
</pre>

運(yùn)行效果:

程序比較簡單筛峭,但是需要注意執(zhí)行步驟:當(dāng)點(diǎn)擊了“加載圖片”按鈕后啟動一個新的線程,這個線程在演示中大概用了5s左右脚乡,在這5s內(nèi)UI線程是不會阻塞的蜒滩,用戶可以進(jìn)行其他操作滨达,大約5s之后圖片下載完成,此時(shí)調(diào)用UI線程將圖片顯示到界面中(這個過程瞬間完成)俯艰。另外前面也提到過捡遍,更新UI的時(shí)候使用UI線程,這里調(diào)用了NSObject的分類擴(kuò)展方法竹握,調(diào)用UI線程完成更新画株。

<a name="t6"></a>多個線程并發(fā)

上面這個演示并沒有演示多個子線程操作之間的關(guān)系,現(xiàn)在不妨在界面中多加載幾張圖片啦辐,每個圖片都來自遠(yuǎn)程請求谓传。

大家應(yīng)該注意到不管是使用+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(id)argument 方法還是使用- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait方法都只能傳一個參數(shù)芹关,由于更新圖片需要傳遞UIImageView的索引和圖片數(shù)據(jù)续挟,因此這里不妨定義一個類保存圖片索引和圖片數(shù)據(jù)以供后面使用。

KCImageData.h

<pre name="code" class="code" style="margin: 0px; white-space: pre-wrap; word-wrap: break-word; padding: 0px 10px; list-style-type: none; list-style-image: none; font-family: 'Courier New', Courier, monospace;">//
// KCImageData.h
// MultiThread
//
// Created by Kenshin Cui on 14-3-22.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

import <Foundation/Foundation.h>

@interface KCImageData : NSObject

pragma mark 索引

@property (nonatomic,assign) int index;

pragma mark 圖片數(shù)據(jù)

@property (nonatomic,strong) NSData *data;

@end</pre>

接下來將創(chuàng)建多個UIImageView并創(chuàng)建多個線程用于往UIImageView中填充圖片侥衬。

KCMainViewController.m

<pre name="code" class="code" style="margin: 0px; white-space: pre-wrap; word-wrap: break-word; padding: 0px 10px; list-style-type: none; list-style-image: none; font-family: 'Courier New', Courier, monospace;">//
// NSThread實(shí)現(xiàn)多線程
// MultiThread
//
// Created by Kenshin Cui on 14-3-22.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

import "KCMainViewController.h"

import "KCImageData.h"

define ROW_COUNT 5

define COLUMN_COUNT 3

define ROW_HEIGHT 100

define ROW_WIDTH ROW_HEIGHT

define CELL_SPACING 10

@interface KCMainViewController (){
NSMutableArray *_imageViews;
}

@end

@implementation KCMainViewController

  • (void)viewDidLoad {
    [super viewDidLoad];

    [self layoutUI];
    }

pragma mark 界面布局

-(void)layoutUI{
//創(chuàng)建多個圖片控件用于顯示圖片
_imageViews=[NSMutableArray array];
for (int r=0; r<ROW_COUNT; r++) {
for (int c=0; c<COLUMN_COUNT; c++) {
UIImageView imageView=[[UIImageView alloc]initWithFrame:CGRectMake(cROW_WIDTH+(cCELL_SPACING), rROW_HEIGHT+(r*CELL_SPACING ), ROW_WIDTH, ROW_HEIGHT)];
imageView.contentMode=UIViewContentModeScaleAspectFit;
// imageView.backgroundColor=[UIColor redColor];
[self.view addSubview:imageView];
[_imageViews addObject:imageView];

    }
}

UIButton *button=[UIButton buttonWithType:UIButtonTypeRoundedRect];
button.frame=CGRectMake(50, 500, 220, 25);
[button setTitle:@"加載圖片" forState:UIControlStateNormal];
//添加方法
[button addTarget:self action:@selector(loadImageWithMultiThread) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];

}

pragma mark 將圖片顯示到界面

-(void)updateImage:(KCImageData *)imageData{
UIImage *image=[UIImage imageWithData:imageData.data];
UIImageView *imageView= _imageViews[imageData.index];
imageView.image=image;
}

pragma mark 請求圖片數(shù)據(jù)

-(NSData *)requestData:(int )index{
//對于多線程操作建議把線程操作放到@autoreleasepool中
@autoreleasepool {
NSURL *url=[NSURL URLWithString:@"http://images.apple.com/iphone-6/overview/images/biggest_right_large.png"];
NSData *data=[NSData dataWithContentsOfURL:url];
return data;
}
}

pragma mark 加載圖片

-(void)loadImage:(NSNumber *)index{
// NSLog(@"%i",i);
//currentThread方法可以取得當(dāng)前操作線程
NSLog(@"current thread:%@",[NSThread currentThread]);

int i=[index integerValue];

// NSLog(@"%i",i);//未必按順序輸出

NSData *data= [self requestData:i];

KCImageData *imageData=[[KCImageData alloc]init];
imageData.index=i;
imageData.data=data;
[self performSelectorOnMainThread:@selector(updateImage:) withObject:imageData waitUntilDone:YES];

}

pragma mark 多線程下載圖片

-(void)loadImageWithMultiThread{
//創(chuàng)建多個線程用于填充圖片
for (int i=0; i<ROW_COUNT*COLUMN_COUNT; ++i) {
// [NSThread detachNewThreadSelector:@selector(loadImage:) toTarget:self withObject:[NSNumber numberWithInt:i]];
NSThread *thread=[[NSThread alloc]initWithTarget:self selector:@selector(loadImage:) object:[NSNumber numberWithInt:i]];
thread.name=[NSString stringWithFormat:@"myThread%i",i];//設(shè)置線程名稱
[thread start];
}
}
@end</pre>

通過NSThread的currentThread可以取得當(dāng)前操作的線程诗祸,其中會記錄線程名稱name和編號number,需要注意主線程編號永遠(yuǎn)為1轴总。多個線程雖然按順序啟動直颅,但是實(shí)際執(zhí)行未必按照順序加載照片(loadImage:方法未必依次創(chuàng)建,可以通過在loadImage:中打印索引查看)怀樟,因?yàn)榫€程啟動后僅僅處于就緒狀態(tài)功偿,實(shí)際是否執(zhí)行要由CPU根據(jù)當(dāng)前狀態(tài)調(diào)度。

從上面的運(yùn)行效果大家不難發(fā)現(xiàn)往堡,圖片并未按順序加載械荷,原因有兩個:第一,每個線程的實(shí)際執(zhí)行順序并不一定按順序執(zhí)行(雖然是按順序啟動)投蝉;第二养葵,每個線程執(zhí)行時(shí)實(shí)際網(wǎng)絡(luò)狀況很可能不一致。當(dāng)然網(wǎng)絡(luò)問題無法改變瘩缆,只能盡可能讓網(wǎng)速更快关拒,但是可以改變線程的優(yōu)先級,讓15個線程優(yōu)先執(zhí)行某個線程庸娱。線程優(yōu)先級范圍為0~1着绊,值越大優(yōu)先級越高,每個線程的優(yōu)先級默認(rèn)為0.5熟尉。修改圖片下載方法如下循帐,改變最后一張圖片加載的優(yōu)先級娇跟,這樣可以提高它被優(yōu)先加載的幾率,但是它也未必就第一個加載破镰。因?yàn)槭紫绕渌€程是先啟動的,其次網(wǎng)絡(luò)狀況我們沒辦法修改:

<pre name="code" class="code" style="margin: 0px; white-space: pre-wrap; word-wrap: break-word; padding: 0px 10px; list-style-type: none; list-style-image: none; font-family: 'Courier New', Courier, monospace;">-(void)loadImageWithMultiThread{
NSMutableArray threads=[NSMutableArray array];
int count=ROW_COUNT
COLUMN_COUNT;
//創(chuàng)建多個線程用于填充圖片
for (int i=0; i<count; ++i) {
// [NSThread detachNewThreadSelector:@selector(loadImage:) toTarget:self withObject:[NSNumber numberWithInt:i]];
NSThread *thread=[[NSThread alloc]initWithTarget:self selector:@selector(loadImage:) object:[NSNumber numberWithInt:i]];
thread.name=[NSString stringWithFormat:@"myThread%i",i];//設(shè)置線程名稱
if(i==(count-1)){
thread.threadPriority=1.0;
}else{
thread.threadPriority=0.0;
}
[threads addObject:thread];
}

for (int i=0; i<count; i++) {
    NSThread *thread=threads[i];
    [thread start];
}

}</pre>

<a name="t7"></a>線程狀態(tài)

在線程操作過程中可以讓某個線程休眠等待,優(yōu)先執(zhí)行其他線程操作,而且在這個過程中還可以修改某個線程的狀態(tài)或者終止某個指定線程一铅。為了解決上面優(yōu)先加載最后一張圖片的問題,不妨讓其他線程先休眠一會等待最后一個線程執(zhí)行堕油。修改圖片加載方法如下即可:

<pre name="code" class="code" style="margin: 0px; white-space: pre-wrap; word-wrap: break-word; padding: 0px 10px; list-style-type: none; list-style-image: none; font-family: 'Courier New', Courier, monospace;">-(NSData )requestData:(int )index{
//對于多線程操作建議把線程操作放到@autoreleasepool中
@autoreleasepool {
//對非最后一張圖片加載線程休眠2秒
if (index!=(ROW_COUNT
COLUMN_COUNT-1)) {
[NSThread sleepForTimeInterval:2.0];
}
NSURL *url=[NSURL URLWithString:_imageNames[index]];
NSData *data=[NSData dataWithContentsOfURL:url];

    return data;
}

}</pre>

在這里讓其他線程休眠2秒潘飘,此時(shí)你就會看到最后一張圖片總是第一個加載(除非網(wǎng)速特別差)。

線程狀態(tài)分為isExecuting(正在執(zhí)行)掉缺、isFinished(已經(jīng)完成)卜录、isCancellled(已經(jīng)取消)三種。其中取消狀態(tài)程序可以干預(yù)設(shè)置眶明,只要調(diào)用線程的cancel方法即可艰毒。但是需要注意在主線程中僅僅能設(shè)置線程狀態(tài),并不能真正停止當(dāng)前線程赘来,如果要終止線程必須在線程中調(diào)用exist方法现喳,這是一個靜態(tài)方法,調(diào)用該方法可以退出當(dāng)前線程犬辰。

假設(shè)在圖片加載過程中點(diǎn)擊停止按鈕讓沒有完成的線程停止加載,可以改造程序如下:

<pre name="code" class="code" style="margin: 0px; white-space: pre-wrap; word-wrap: break-word; padding: 0px 10px; list-style-type: none; list-style-image: none; font-family: 'Courier New', Courier, monospace;">//
// NSThread實(shí)現(xiàn)多線程
// MultiThread
//
// Created by Kenshin Cui on 14-3-22.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

import "KCMainViewController.h"

import "KCImageData.h"

define ROW_COUNT 5

define COLUMN_COUNT 3

define ROW_HEIGHT 100

define ROW_WIDTH ROW_HEIGHT

define CELL_SPACING 10

@interface KCMainViewController (){
NSMutableArray *_imageViews;
NSMutableArray *_imageNames;
NSMutableArray *_threads;
}

@end

@implementation KCMainViewController

  • (void)viewDidLoad {
    [super viewDidLoad];

    [self layoutUI];
    }

pragma mark 界面布局

-(void)layoutUI{
//創(chuàng)建多個圖片空間用于顯示圖片
_imageViews=[NSMutableArray array];
for (int r=0; r<ROW_COUNT; r++) {
for (int c=0; c<COLUMN_COUNT; c++) {
UIImageView imageView=[[UIImageView alloc]initWithFrame:CGRectMake(cROW_WIDTH+(cCELL_SPACING), rROW_HEIGHT+(r*CELL_SPACING ), ROW_WIDTH, ROW_HEIGHT)];
imageView.contentMode=UIViewContentModeScaleAspectFit;
// imageView.backgroundColor=[UIColor redColor];
[self.view addSubview:imageView];
[_imageViews addObject:imageView];

    }
}

//加載按鈕
UIButton *buttonStart=[UIButton buttonWithType:UIButtonTypeRoundedRect];
buttonStart.frame=CGRectMake(50, 500, 100, 25);
[buttonStart setTitle:@"加載圖片" forState:UIControlStateNormal];
[buttonStart addTarget:self action:@selector(loadImageWithMultiThread) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:buttonStart];

//停止按鈕
UIButton *buttonStop=[UIButton buttonWithType:UIButtonTypeRoundedRect];
buttonStop.frame=CGRectMake(160, 500, 100, 25);
[buttonStop setTitle:@"停止加載" forState:UIControlStateNormal];
[buttonStop addTarget:self action:@selector(stopLoadImage) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:buttonStop];

//創(chuàng)建圖片鏈接
_imageNames=[NSMutableArray array];
[_imageNames addObject:@    for (int i=0; i<IMAGE_COUNT; i++) {
    [_imageNames addObject:[NSString stringWithFormat:@"http://images.cnblogs.com/cnblogs_com/kenshincui/613474/o_%i.jpg",i]];
}    

}

pragma mark 將圖片顯示到界面

-(void)updateImage:(KCImageData *)imageData{
UIImage *image=[UIImage imageWithData:imageData.data];
UIImageView *imageView= _imageViews[imageData.index];
imageView.image=image;
}

pragma mark 請求圖片數(shù)據(jù)

-(NSData *)requestData:(int )index{
//對于多線程操作建議把線程操作放到@autoreleasepool中
@autoreleasepool {
NSURL *url=[NSURL URLWithString:_imageNames[index]];
NSData *data=[NSData dataWithContentsOfURL:url];

    return data;
}

}

pragma mark 加載圖片

-(void)loadImage:(NSNumber *)index{
int i=[index integerValue];

NSData *data= [self requestData:i];

NSThread *currentThread=[NSThread currentThread];

// 如果當(dāng)前線程處于取消狀態(tài)冰单,則退出當(dāng)前線程
if (currentThread.isCancelled) {
NSLog(@"thread(%@) will be cancelled!",currentThread);
[NSThread exit];//取消當(dāng)前線程
}

KCImageData *imageData=[[KCImageData alloc]init];
imageData.index=i;
imageData.data=data;
[self performSelectorOnMainThread:@selector(updateImage:) withObject:imageData waitUntilDone:YES];

}

pragma mark 多線程下載圖片

-(void)loadImageWithMultiThread{
int count=ROW_COUNT*COLUMN_COUNT;
_threads=[NSMutableArray arrayWithCapacity:count];

//創(chuàng)建多個線程用于填充圖片
for (int i=0; i<count; ++i) {
    NSThread *thread=[[NSThread alloc]initWithTarget:self selector:@selector(loadImage:) object:[NSNumber numberWithInt:i]];
    thread.name=[NSString stringWithFormat:@"myThread%i",i];//設(shè)置線程名稱
    [_threads addObject:thread];
}
//循環(huán)啟動線程
for (int i=0; i<count; ++i) {
    NSThread *thread= _threads[i];
    [thread start];
}

}

pragma mark 停止加載圖片

-(void)stopLoadImage{
for (int i=0; i<ROW_COUNT*COLUMN_COUNT; i++) {
NSThread *thread= _threads[i];
//判斷線程是否完成幌缝,如果沒有完成則設(shè)置為取消狀態(tài)
//注意設(shè)置為取消狀態(tài)僅僅是改變了線程狀態(tài)而言,并不能終止線程
if (!thread.isFinished) {
[thread cancel];

    }
}

}
@end</pre>

運(yùn)行效果(點(diǎn)擊加載大概1秒后點(diǎn)擊停止加載):

使用NSThread在進(jìn)行多線程開發(fā)過程中操作比較簡單诫欠,但是要控制線程執(zhí)行順序并不容易(前面萬不得已采用了休眠的方法)涵卵,另外在這個過程中如果打印線程會發(fā)現(xiàn)循環(huán)幾次就創(chuàng)建了幾個線程,這在實(shí)際開發(fā)過程中是不得不考慮的問題荒叼,因?yàn)槊總€線程的創(chuàng)建也是相當(dāng)占用系統(tǒng)開銷的轿偎。

<a name="t8"></a>擴(kuò)展--NSObject分類擴(kuò)展方法

為了簡化多線程開發(fā)過程,蘋果官方對NSObject進(jìn)行分類擴(kuò)展(本質(zhì)還是創(chuàng)建NSThread)被廓,對于簡單的多線程操作可以直接使用這些擴(kuò)展方法坏晦。

- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg:在后臺執(zhí)行一個操作,本質(zhì)就是重新創(chuàng)建一個線程執(zhí)行當(dāng)前方法嫁乘。

*- (void)performSelector:(SEL)aSelector onThread:(NSThread )thr withObject:(id)arg waitUntilDone:(BOOL)wait:在指定的線程上執(zhí)行一個方法昆婿,需要用戶創(chuàng)建一個線程對象。

- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait:在主線程上執(zhí)行一個方法(前面已經(jīng)使用過)蜓斧。

例如前面加載圖多個圖片的方法仓蛆,可以改為后臺線程執(zhí)行:

<pre name="code" class="code" style="margin: 0px; white-space: pre-wrap; word-wrap: break-word; padding: 0px 10px; list-style-type: none; list-style-image: none; font-family: 'Courier New', Courier, monospace;">-(void)loadImageWithMultiThread{
int count=ROW_COUNT*COLUMN_COUNT;

for (int i=0; i<count; ++i) {
    [self performSelectorInBackground:@selector(loadImage:) withObject:[NSNumber numberWithInt:i]];
}

}</pre>

<a name="t9"></a>NSOperation

使用NSOperation和NSOperationQueue進(jìn)行多線程開發(fā)類似于C#中的線程池,只要將一個NSOperation(實(shí)際開中需要使用其子類NSInvocationOperation挎春、NSBlockOperation)放到NSOperationQueue這個隊(duì)列中線程就會依次啟動看疙。NSOperationQueue負(fù)責(zé)管理豆拨、執(zhí)行所有的NSOperation,在這個過程中可以更加容易的管理線程總數(shù)和控制線程之間的依賴關(guān)系能庆。

NSOperation有兩個常用子類用于創(chuàng)建線程操作:NSInvocationOperation和NSBlockOperation施禾,兩種方式本質(zhì)沒有區(qū)別,但是是后者使用Block形式進(jìn)行代碼組織相味,使用相對方便拾积。

<a name="t10"></a>NSInvocationOperation

首先使用NSInvocationOperation進(jìn)行一張圖片的加載演示,整個過程就是:創(chuàng)建一個操作丰涉,在這個操作中指定調(diào)用方法和參數(shù)拓巧,然后加入到操作隊(duì)列。其他代碼基本不用修改一死,直接修加載圖片方法如下:

<pre name="code" class="code" style="margin: 0px; white-space: pre-wrap; word-wrap: break-word; padding: 0px 10px; list-style-type: none; list-style-image: none; font-family: 'Courier New', Courier, monospace;">-(void)loadImageWithMultiThread{
/*創(chuàng)建一個調(diào)用操作
object:調(diào)用方法參數(shù)
*/
NSInvocationOperation *invocationOperation=[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(loadImage) object:nil];
//創(chuàng)建完NSInvocationOperation對象并不會調(diào)用肛度,它由一個start方法啟動操作,但是注意如果直接調(diào)用start方法投慈,則此操作會在主線程中調(diào)用承耿,一般不會這么操作,而是添加到NSOperationQueue中
// [invocationOperation start];

//創(chuàng)建操作隊(duì)列
NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
//注意添加到操作隊(duì)后,隊(duì)列會開啟一個線程執(zhí)行此操作
[operationQueue addOperation:invocationOperation];

}</pre>

<a name="t11"></a>NSBlockOperation

下面采用NSBlockOperation創(chuàng)建多個線程加載圖片伪煤。

<pre name="code" class="code" style="margin: 0px; white-space: pre-wrap; word-wrap: break-word; padding: 0px 10px; list-style-type: none; list-style-image: none; font-family: 'Courier New', Courier, monospace;">//
// NSOperation實(shí)現(xiàn)多線程
// MultiThread
//
// Created by Kenshin Cui on 14-3-22.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

import "KCMainViewController.h"

import "KCImageData.h"

define ROW_COUNT 5

define COLUMN_COUNT 3

define ROW_HEIGHT 100

define ROW_WIDTH ROW_HEIGHT

define CELL_SPACING 10

@interface KCMainViewController (){
NSMutableArray *_imageViews;
NSMutableArray *_imageNames;
}

@end

@implementation KCMainViewController

  • (void)viewDidLoad {
    [super viewDidLoad];

    [self layoutUI];
    }

pragma mark 界面布局

-(void)layoutUI{
//創(chuàng)建多個圖片控件用于顯示圖片
_imageViews=[NSMutableArray array];
for (int r=0; r<ROW_COUNT; r++) {
for (int c=0; c<COLUMN_COUNT; c++) {
UIImageView imageView=[[UIImageView alloc]initWithFrame:CGRectMake(cROW_WIDTH+(cCELL_SPACING), rROW_HEIGHT+(r*CELL_SPACING ), ROW_WIDTH, ROW_HEIGHT)];
imageView.contentMode=UIViewContentModeScaleAspectFit;
// imageView.backgroundColor=[UIColor redColor];
[self.view addSubview:imageView];
[_imageViews addObject:imageView];

    }
}

UIButton *button=[UIButton buttonWithType:UIButtonTypeRoundedRect];
button.frame=CGRectMake(50, 500, 220, 25);
[button setTitle:@"加載圖片" forState:UIControlStateNormal];
//添加方法
[button addTarget:self action:@selector(loadImageWithMultiThread) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];

//創(chuàng)建圖片鏈接
_imageNames=[NSMutableArray array];
for (int i=0; i<IMAGE_COUNT; i++) {
    [_imageNames addObject:[NSString stringWithFormat:@"http://images.cnblogs.com/cnblogs_com/kenshincui/613474/o_%i.jpg",i]];
}    

}

pragma mark 將圖片顯示到界面

-(void)updateImageWithData:(NSData *)data andIndex:(int )index{
UIImage *image=[UIImage imageWithData:data];
UIImageView *imageView= _imageViews[index];
imageView.image=image;
}

pragma mark 請求圖片數(shù)據(jù)

-(NSData *)requestData:(int )index{
//對于多線程操作建議把線程操作放到@autoreleasepool中
@autoreleasepool {
NSURL *url=[NSURL URLWithString:_imageNames[index]];
NSData *data=[NSData dataWithContentsOfURL:url];

    return data;
}

}

pragma mark 加載圖片

-(void)loadImage:(NSNumber *)index{
int i=[index integerValue];

//請求數(shù)據(jù)
NSData *data= [self requestData:i];
NSLog(@"%@",[NSThread currentThread]);
//更新UI界面,此處調(diào)用了主線程隊(duì)列的方法(mainQueue是UI主線程)
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
    [self updateImageWithData:data andIndex:i];
}];

}

pragma mark 多線程下載圖片

-(void)loadImageWithMultiThread{
int count=ROW_COUNT*COLUMN_COUNT;
//創(chuàng)建操作隊(duì)列
NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
operationQueue.maxConcurrentOperationCount=5;//設(shè)置最大并發(fā)線程數(shù)
//創(chuàng)建多個線程用于填充圖片
for (int i=0; i<count; ++i) {
//方法1:創(chuàng)建操作塊添加到隊(duì)列
// //創(chuàng)建多線程操作
// NSBlockOperation *blockOperation=[NSBlockOperation blockOperationWithBlock:^{
// [self loadImage:[NSNumber numberWithInt:i]];
// }];
// //創(chuàng)建操作隊(duì)列
//
// [operationQueue addOperation:blockOperation];

    //方法2:直接使用操隊(duì)列添加操作
    [operationQueue addOperationWithBlock:^{
        [self loadImage:[NSNumber numberWithInt:i]];
    }];

}

}
@end</pre>

對比之前NSThread加載張圖片很發(fā)現(xiàn)核心代碼簡化了不少加袋,這里著重強(qiáng)調(diào)兩點(diǎn):

  1. 使用NSBlockOperation方法,所有的操作不必單獨(dú)定義方法抱既,同時(shí)解決了只能傳遞一個參數(shù)的問題职烧。
  2. 調(diào)用主線程隊(duì)列的addOperationWithBlock:方法進(jìn)行UI更新,不用再定義一個參數(shù)實(shí)體(之前必須定義一個KCImageData解決只能傳遞一個參數(shù)的問題)防泵。
  3. 使用NSOperation進(jìn)行多線程開發(fā)可以設(shè)置最大并發(fā)線程蚀之,有效的對線程進(jìn)行了控制(上面的代碼運(yùn)行起來你會發(fā)現(xiàn)打印當(dāng)前進(jìn)程時(shí)只有有限的線程被創(chuàng)建,如上面的代碼設(shè)置最大線程數(shù)為5捷泞,則圖片基本上是五個一次加載的)足删。

<a name="t12"></a>線程執(zhí)行順序

前面使用NSThread很難控制線程的執(zhí)行順序,但是使用NSOperation就容易多了锁右,每個NSOperation可以設(shè)置依賴線程失受。假設(shè)操作A依賴于操作B,線程操作隊(duì)列在啟動線程時(shí)就會首先執(zhí)行B操作骡湖,然后執(zhí)行A贱纠。對于前面優(yōu)先加載最后一張圖的需求,只要設(shè)置前面的線程操作的依賴線程為最后一個操作即可响蕴。修改圖片加載方法如下:

<pre name="code" class="code" style="margin: 0px; white-space: pre-wrap; word-wrap: break-word; padding: 0px 10px; list-style-type: none; list-style-image: none; font-family: 'Courier New', Courier, monospace;">-(void)loadImageWithMultiThread{
int count=ROW_COUNT*COLUMN_COUNT;
//創(chuàng)建操作隊(duì)列
NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
operationQueue.maxConcurrentOperationCount=5;//設(shè)置最大并發(fā)線程數(shù)

NSBlockOperation *lastBlockOperation=[NSBlockOperation blockOperationWithBlock:^{
    [self loadImage:[NSNumber numberWithInt:(count-1)]];
}];
//創(chuàng)建多個線程用于填充圖片
for (int i=0; i<count-1; ++i) {
    //方法1:創(chuàng)建操作塊添加到隊(duì)列
    //創(chuàng)建多線程操作
    NSBlockOperation *blockOperation=[NSBlockOperation blockOperationWithBlock:^{
        [self loadImage:[NSNumber numberWithInt:i]];
    }];
    //設(shè)置依賴操作為最后一張圖片加載操作
    [blockOperation addDependency:lastBlockOperation];

    [operationQueue addOperation:blockOperation];

}
//將最后一個圖片的加載操作加入線程隊(duì)列
[operationQueue addOperation:lastBlockOperation];

}</pre>

運(yùn)行效果:

可以看到雖然加載最后一張圖片的操作最后被加入到操作隊(duì)列谆焊,但是它卻是被第一個執(zhí)行的。操作依賴關(guān)系可以設(shè)置多個浦夷,例如A依賴于B辖试、B依賴于C…但是千萬不要設(shè)置為循環(huán)依賴關(guān)系(例如A依賴于B辜王,B依賴于C,C又依賴于A)罐孝,否則是不會被執(zhí)行的呐馆。

<a name="t13"></a>GCD

GCD(Grand Central Dispatch)是基于C語言開發(fā)的一套多線程開發(fā)機(jī)制,也是目前蘋果官方推薦的多線程開發(fā)方法莲兢。前面也說過三種開發(fā)中GCD抽象層次最高,當(dāng)然是用起來也最簡單,只是它基于C語言開發(fā)承疲,并不像NSOperation是面向?qū)ο蟮拈_發(fā)啊研,而是完全面向過程的麸锉。對于熟悉C#異步調(diào)用的朋友對于GCD學(xué)習(xí)起來應(yīng)該很快,因?yàn)樗cC#中的異步調(diào)用基本是一樣的。這種機(jī)制相比較于前面兩種多線程開發(fā)方式最顯著的優(yōu)點(diǎn)就是它對于多核運(yùn)算更加有效。

GCD中也有一個類似于NSOperationQueue的隊(duì)列俩功,GCD統(tǒng)一管理整個隊(duì)列中的任務(wù)。但是GCD中的隊(duì)列分為并行隊(duì)列和串行隊(duì)列兩類:

  • 串行隊(duì)列:只有一個線程蘸朋,加入到隊(duì)列中的操作按添加順序依次執(zhí)行。
  • 并發(fā)隊(duì)列:有多個線程炼彪,操作進(jìn)來之后它會將這些隊(duì)列安排在可用的處理器上辐马,同時(shí)保證先進(jìn)來的任務(wù)優(yōu)先處理。

其實(shí)在GCD中還有一個特殊隊(duì)列就是主隊(duì)列喜爷,用來執(zhí)行主線程上的操作任務(wù)(從前面的演示中可以看到其實(shí)在NSOperation中也有一個主隊(duì)列)术幔。

<a name="t14"></a>串行隊(duì)列

使用串行隊(duì)列時(shí)首先要創(chuàng)建一個串行隊(duì)列泛源,然后調(diào)用異步調(diào)用方法没龙,在此方法中傳入串行隊(duì)列和線程操作即可自動執(zhí)行。下面使用線程隊(duì)列演示圖片的加載過程,你會發(fā)現(xiàn)多張圖片會按順序加載兜畸,因?yàn)楫?dāng)前隊(duì)列中只有一個線程努释。

<pre name="code" class="code" style="margin: 0px; white-space: pre-wrap; word-wrap: break-word; padding: 0px 10px; list-style-type: none; list-style-image: none; font-family: 'Courier New', Courier, monospace;">//
// GCD實(shí)現(xiàn)多線程
// MultiThread
//
// Created by Kenshin Cui on 14-3-22.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

import "KCMainViewController.h"

import "KCImageData.h"

define ROW_COUNT 5

define COLUMN_COUNT 3

define ROW_HEIGHT 100

define ROW_WIDTH ROW_HEIGHT

define CELL_SPACING 10

@interface KCMainViewController (){
NSMutableArray *_imageViews;
NSMutableArray *_imageNames;
}

@end

@implementation KCMainViewController

  • (void)viewDidLoad {
    [super viewDidLoad];

    [self layoutUI];
    }

pragma mark 界面布局

-(void)layoutUI{
//創(chuàng)建多個圖片控件用于顯示圖片
_imageViews=[NSMutableArray array];
for (int r=0; r<ROW_COUNT; r++) {
for (int c=0; c<COLUMN_COUNT; c++) {
UIImageView imageView=[[UIImageView alloc]initWithFrame:CGRectMake(cROW_WIDTH+(cCELL_SPACING), rROW_HEIGHT+(r*CELL_SPACING ), ROW_WIDTH, ROW_HEIGHT)];
imageView.contentMode=UIViewContentModeScaleAspectFit;
// imageView.backgroundColor=[UIColor redColor];
[self.view addSubview:imageView];
[_imageViews addObject:imageView];

    }
}

UIButton *button=[UIButton buttonWithType:UIButtonTypeRoundedRect];
button.frame=CGRectMake(50, 500, 220, 25);
[button setTitle:@"加載圖片" forState:UIControlStateNormal];
//添加方法
[button addTarget:self action:@selector(loadImageWithMultiThread) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];

//創(chuàng)建圖片鏈接
_imageNames=[NSMutableArray array];
for (int i=0; i<ROW_COUNT*COLUMN_COUNT; i++) {
    [_imageNames addObject:[NSString stringWithFormat:@"http://images.cnblogs.com/cnblogs_com/kenshincui/613474/o_%i.jpg",i]];
}

}

pragma mark 將圖片顯示到界面

-(void)updateImageWithData:(NSData *)data andIndex:(int )index{
UIImage *image=[UIImage imageWithData:data];
UIImageView *imageView= _imageViews[index];
imageView.image=image;
}

pragma mark 請求圖片數(shù)據(jù)

-(NSData *)requestData:(int )index{
NSURL *url=[NSURL URLWithString:_imageNames[index]];
NSData *data=[NSData dataWithContentsOfURL:url];

return data;

}

pragma mark 加載圖片

-(void)loadImage:(NSNumber *)index{

//如果在串行隊(duì)列中會發(fā)現(xiàn)當(dāng)前線程打印變化完全一樣,因?yàn)樗麄冊谝粋€線程中
NSLog(@"thread is :%@",[NSThread currentThread]);

int i=[index integerValue];
//請求數(shù)據(jù)
NSData *data= [self requestData:i];
//更新UI界面,此處調(diào)用了GCD主線程隊(duì)列的方法
dispatch_queue_t mainQueue= dispatch_get_main_queue();
dispatch_sync(mainQueue, ^{
    [self updateImageWithData:data andIndex:i];
});

}

pragma mark 多線程下載圖片

-(void)loadImageWithMultiThread{
int count=ROW_COUNT*COLUMN_COUNT;

/*創(chuàng)建一個串行隊(duì)列
 第一個參數(shù):隊(duì)列名稱
 第二個參數(shù):隊(duì)列類型
*/
dispatch_queue_t serialQueue=dispatch_queue_create("myThreadQueue1", DISPATCH_QUEUE_SERIAL);//注意queue對象不是指針類型
//創(chuàng)建多個線程用于填充圖片
for (int i=0; i<count; ++i) {
    //異步執(zhí)行隊(duì)列任務(wù)
    dispatch_async(serialQueue, ^{
        [self loadImage:[NSNumber numberWithInt:i]];
    });

}
//非ARC環(huán)境請釋放

// dispatch_release(seriQueue);
}
@end</pre>

運(yùn)行效果:

在上面的代碼中更新UI還使用了GCD方法的主線程隊(duì)列dispatch_get_main_queue()咬摇,其實(shí)這與前面兩種主線程更新UI沒有本質(zhì)的區(qū)別伐蒂。

<a name="t15"></a>并發(fā)隊(duì)列

并發(fā)隊(duì)列同樣是使用dispatch_queue_create()方法創(chuàng)建,只是最后一個參數(shù)指定為DISPATCH_QUEUE_CONCURRENT進(jìn)行創(chuàng)建肛鹏,但是在實(shí)際開發(fā)中我們通常不會重新創(chuàng)建一個并發(fā)隊(duì)列而是使用dispatch_get_global_queue()方法取得一個全局的并發(fā)隊(duì)列(當(dāng)然如果有多個并發(fā)隊(duì)列可以使用前者創(chuàng)建)逸邦。下面通過并行隊(duì)列演示一下多個圖片的加載。代碼與上面串行隊(duì)列加載類似在扰,只需要修改照片加載方法如下:

<pre name="code" class="code" style="margin: 0px; white-space: pre-wrap; word-wrap: break-word; padding: 0px 10px; list-style-type: none; list-style-image: none; font-family: 'Courier New', Courier, monospace;">-(void)loadImageWithMultiThread{
int count=ROW_COUNT*COLUMN_COUNT;

/*取得全局隊(duì)列
 第一個參數(shù):線程優(yōu)先級
 第二個參數(shù):標(biāo)記參數(shù)缕减,目前沒有用,一般傳入0
*/
dispatch_queue_t globalQueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//創(chuàng)建多個線程用于填充圖片
for (int i=0; i<count; ++i) {
    //異步執(zhí)行隊(duì)列任務(wù)
    dispatch_async(globalQueue, ^{
        [self loadImage:[NSNumber numberWithInt:i]];
    });
}

}</pre>

運(yùn)行效果:

細(xì)心的朋友肯定會思考芒珠,既然可以使用dispatch_async()異步調(diào)用方法桥狡,是不是還有同步方法,確實(shí)如此皱卓,在GCD中還有一個dispatch_sync()方法裹芝。假設(shè)將上面的代碼修改為同步調(diào)用,可以看到如下效果:

可以看點(diǎn)擊按鈕后按鈕無法再次點(diǎn)擊娜汁,因?yàn)樗袌D片的加載全部在主線程中(可以打印線程查看)嫂易,主線程被阻塞,造成圖片最終是一次性顯示掐禁×担可以得出結(jié)論:

  • 在GDC中一個操作是多線程執(zhí)行還是單線程執(zhí)行取決于當(dāng)前隊(duì)列類型和執(zhí)行方法,只有隊(duì)列類型為并行隊(duì)列并且使用異步方法執(zhí)行時(shí)才能在多個線程中執(zhí)行傅事。
  • 串行隊(duì)列可以按順序執(zhí)行,并行隊(duì)列的異步方法無法確定執(zhí)行順序灼芭。
  • UI界面的更新最好采用同步方法,其他操作采用異步方法巍佑。
  • GCD中多線程操作方法不需要使用@autoreleasepool萤衰,GCD會管理內(nèi)存。

<a name="t16"></a>其他任務(wù)執(zhí)行方法

GCD執(zhí)行任務(wù)的方法并非只有簡單的同步調(diào)用方法和異步調(diào)用方法洒擦,還有其他一些常用方法:

  1. dispatch_apply():重復(fù)執(zhí)行某個任務(wù),但是注意這個方法沒有辦法異步執(zhí)行(為了不阻塞線程可以使用dispatch_async()包裝一下再執(zhí)行)怕膛。
  2. dispatch_once():單次執(zhí)行一個任務(wù)熟嫩,此方法中的任務(wù)只會執(zhí)行一次,重復(fù)調(diào)用也沒辦法重復(fù)執(zhí)行(單例模式中常用此方法)褐捻。
  3. dispatch_time():延遲一定的時(shí)間后執(zhí)行掸茅。
  4. dispatch_barrier_async():使用此方法創(chuàng)建的任務(wù)首先會查看隊(duì)列中有沒有別的任務(wù)要執(zhí)行,如果有柠逞,則會等待已有任務(wù)執(zhí)行完畢再執(zhí)行昧狮;同時(shí)在此方法后添加的任務(wù)必須等待此方法中任務(wù)執(zhí)行后才能執(zhí)行。(利用這個方法可以控制執(zhí)行順序板壮,例如前面先加載最后一張圖片的需求就可以先使用這個方法將最后一張圖片加載的操作添加到隊(duì)列逗鸣,然后調(diào)用dispatch_async()添加其他圖片加載任務(wù))
  5. dispatch_group_async():實(shí)現(xiàn)對任務(wù)分組管理,如果一組任務(wù)全部完成可以通過dispatch_group_notify()方法獲得完成通知(需要定義dispatch_group_t作為分組標(biāo)識)绰精。

<a name="t17"></a>線程同步

說到多線程就不得不提多線程中的鎖機(jī)制撒璧,多線程操作過程中往往多個線程是并發(fā)執(zhí)行的,同一個資源可能被多個線程同時(shí)訪問茬底,造成資源搶奪,這個過程中如果沒有鎖機(jī)制往往會造成重大問題涉馁。舉例來說糠悯,每年春節(jié)都是一票難求,在12306買票的過程中阅悍,成百上千的票瞬間就消失了寻行。不妨假設(shè)某輛車有1千張票,同時(shí)有幾萬人在搶這列車的車票,順利的話前面的人都能買到票入热。但是如果現(xiàn)在只剩下一張票了骄噪,而同時(shí)還有幾千人在購買這張票,雖然在進(jìn)入購票環(huán)節(jié)的時(shí)候會判斷當(dāng)前票數(shù)逻谦,但是當(dāng)前已經(jīng)有100個線程進(jìn)入購票的環(huán)節(jié)滋将,每個線程處理完票數(shù)都會減1,100個線程執(zhí)行完當(dāng)前票數(shù)為-99,遇到這種情況很明顯是不允許的。

要解決資源搶奪問題在iOS中有常用的有兩種方法:一種是使用NSLock同步鎖,另一種是使用@synchronized代碼塊。兩種方法實(shí)現(xiàn)原理是類似的须眷,只是在處理上代碼塊使用起來更加簡單(C#中也有類似的處理機(jī)制synchronized和lock)惠拭。

這里不妨還拿圖片加載來舉例棒呛,假設(shè)現(xiàn)在有9張圖片秀鞭,但是有15個線程都準(zhǔn)備加載這9張圖片拆内,約定不能重復(fù)加載同一張圖片搀矫,這樣就形成了一個資源搶奪的情況融欧。在下面的程序中將創(chuàng)建9張圖片麦到,每次讀取照片鏈接時(shí)首先判斷當(dāng)前鏈接數(shù)是否大于1刺桃,用完一個則立即移除,最多只有9個借杰。在使用同步方法之前先來看一下錯誤的寫法:

<pre name="code" class="code" style="margin: 0px; white-space: pre-wrap; word-wrap: break-word; padding: 0px 10px; list-style-type: none; list-style-image: none; font-family: 'Courier New', Courier, monospace;">//
// 線程同步
// MultiThread
//
// Created by Kenshin Cui on 14-3-22.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

import "KCMainViewController.h"

import "KCImageData.h"

define ROW_COUNT 5

define COLUMN_COUNT 3

define ROW_HEIGHT 100

define ROW_WIDTH ROW_HEIGHT

define CELL_SPACING 10

define IMAGE_COUNT 9

@interface KCMainViewController (){
NSMutableArray *_imageViews;
NSMutableArray *_imageNames;
}

@end

@implementation KCMainViewController

  • (void)viewDidLoad {
    [super viewDidLoad];

    [self layoutUI];
    }

pragma mark 界面布局

-(void)layoutUI{
//創(chuàng)建多個圖片控件用于顯示圖片
_imageViews=[NSMutableArray array];
for (int r=0; r<ROW_COUNT; r++) {
for (int c=0; c<COLUMN_COUNT; c++) {
UIImageView imageView=[[UIImageView alloc]initWithFrame:CGRectMake(cROW_WIDTH+(cCELL_SPACING), rROW_HEIGHT+(r*CELL_SPACING ), ROW_WIDTH, ROW_HEIGHT)];
imageView.contentMode=UIViewContentModeScaleAspectFit;
// imageView.backgroundColor=[UIColor redColor];
[self.view addSubview:imageView];
[_imageViews addObject:imageView];

    }
}

UIButton *button=[UIButton buttonWithType:UIButtonTypeRoundedRect];
button.frame=CGRectMake(50, 500, 220, 25);
[button setTitle:@"加載圖片" forState:UIControlStateNormal];
//添加方法
[button addTarget:self action:@selector(loadImageWithMultiThread) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];

//創(chuàng)建圖片鏈接
_imageNames=[NSMutableArray array];
for (int i=0; i<IMAGE_COUNT; i++) {
    [_imageNames addObject:[NSString stringWithFormat:@"http://images.cnblogs.com/cnblogs_com/kenshincui/613474/o_%i.jpg",i]];
}

}

pragma mark 將圖片顯示到界面

-(void)updateImageWithData:(NSData *)data andIndex:(int )index{
UIImage *image=[UIImage imageWithData:data];
UIImageView *imageView= _imageViews[index];
imageView.image=image;
}

pragma mark 請求圖片數(shù)據(jù)

-(NSData *)requestData:(int )index{
NSData *data;
NSString *name;
if (_imageNames.count>0) {
name=[_imageNames lastObject];
[_imageNames removeObject:name];
}
if(name){
NSURL *url=[NSURL URLWithString:name];
data=[NSData dataWithContentsOfURL:url];
}
return data;
}

pragma mark 加載圖片

-(void)loadImage:(NSNumber *)index{
int i=[index integerValue];
//請求數(shù)據(jù)
NSData *data= [self requestData:i];
//更新UI界面,此處調(diào)用了GCD主線程隊(duì)列的方法
dispatch_queue_t mainQueue= dispatch_get_main_queue();
dispatch_sync(mainQueue, ^{
[self updateImageWithData:data andIndex:i];
});
}

pragma mark 多線程下載圖片

-(void)loadImageWithMultiThread{
int count=ROW_COUNT*COLUMN_COUNT;

dispatch_queue_t globalQueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//創(chuàng)建多個線程用于填充圖片
for (int i=0; i<count; ++i) {
    //異步執(zhí)行隊(duì)列任務(wù)
    dispatch_async(globalQueue, ^{
        [self loadImage:[NSNumber numberWithInt:i]];
    });
}

}
@end</pre>

首先在_imageNames中存儲了9個鏈接用于下載圖片翩隧,然后在requestData:方法中每次只需先判斷_imageNames的個數(shù),如果大于一就讀取一個鏈接加載圖片,隨即把用過的鏈接刪除蔗怠,一切貌似都沒有問題。此時(shí)運(yùn)行程序:

上面這個結(jié)果不一定每次都出現(xiàn),關(guān)鍵要看從_imageNames讀取鏈接旺韭、刪除鏈接的速度,如果足夠快可能不會有任何問題惭嚣,但是如果速度稍慢就會出現(xiàn)上面的情況,很明顯上面情況并不滿足前面的需求迁沫。

分析這個問題造成的原因主:當(dāng)一個線程A已經(jīng)開始獲取圖片鏈接挺智,獲取完之后還沒有來得及從_imageNames中刪除媒怯,另一個線程B已經(jīng)進(jìn)入相應(yīng)代碼中,由于每次讀取的都是_imageNames的最后一個元素,因此后面的線程其實(shí)和前面線程取得的是同一個圖片鏈接這樣就造成圖中看到的情況。要解決這個問題蜒谤,只要保證線程A進(jìn)入相應(yīng)代碼之后B無法進(jìn)入,只有等待A完成相關(guān)操作之后B才能進(jìn)入即可阶祭。下面分別使用NSLock和@synchronized對代碼進(jìn)行修改。

<a name="t18"></a>NSLock

iOS中對于資源搶占的問題可以使用同步鎖NSLock來解決晌姚,使用時(shí)把需要加鎖的代碼(以后暫時(shí)稱這段代碼為”加鎖代碼“)放到NSLock的lock和unlock之間猛遍,一個線程A進(jìn)入加鎖代碼之后由于已經(jīng)加鎖,另一個線程B就無法訪問宽堆,只有等待前一個線程A執(zhí)行完加鎖代碼后解鎖腌紧,B線程才能訪問加鎖代碼。需要注意的是lock和unlock之間的”加鎖代碼“應(yīng)該是搶占資源的讀取和修改代碼畜隶,不要將過多的其他操作代碼放到里面壁肋,否則一個線程執(zhí)行的時(shí)候另一個線程就一直在等待跛锌,就無法發(fā)揮多線程的作用了拌牲。

另外甲馋,在上面的代碼中”搶占資源“_imageNames定義成了成員變量液茎,這么做是不明智的她渴,應(yīng)該定義為“原子屬性”篇亭。對于被搶占資源來說將其定義為原子屬性是一個很好的習(xí)慣末购,因?yàn)橛袝r(shí)候很難保證同一個資源不在別處讀取和修改礼饱。nonatomic屬性讀取的是內(nèi)存數(shù)據(jù)(寄存器計(jì)算好的結(jié)果)剃诅,而atomic就保證直接讀取寄存器的數(shù)據(jù)纠屋,這樣一來就不會出現(xiàn)一個線程正在修改數(shù)據(jù)邦邦,而另一個線程讀取了修改之前(存儲在內(nèi)存中)的數(shù)據(jù),永遠(yuǎn)保證同時(shí)只有一個線程在訪問一個屬性耙旦。

下面的代碼演示了如何使用NSLock進(jìn)行線程同步:

KCMainViewController.h

<pre name="code" class="code" style="margin: 0px; white-space: pre-wrap; word-wrap: break-word; padding: 0px 10px; list-style-type: none; list-style-image: none; font-family: 'Courier New', Courier, monospace;">//
// KCMainViewController.h
// MultiThread
//
// Created by Kenshin Cui on 14-3-22.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

import <UIKit/UIKit.h>

@interface KCMainViewController : UIViewController

@property (atomic,strong) NSMutableArray *imageNames;
@end</pre>

KCMainViewController.m

<pre name="code" class="code" style="margin: 0px; white-space: pre-wrap; word-wrap: break-word; padding: 0px 10px; list-style-type: none; list-style-image: none; font-family: 'Courier New', Courier, monospace;">//
// 線程同步
// MultiThread
//
// Created by Kenshin Cui on 14-3-22.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

import "KCMainViewController.h"

import "KCImageData.h"

define ROW_COUNT 5

define COLUMN_COUNT 3

define ROW_HEIGHT 100

define ROW_WIDTH ROW_HEIGHT

define CELL_SPACING 10

define IMAGE_COUNT 9

@interface KCMainViewController (){
NSMutableArray *_imageViews;
NSLock *_lock;
}

@end

@implementation KCMainViewController

  • (void)viewDidLoad {
    [super viewDidLoad];

    [self layoutUI];
    }

pragma mark 界面布局

-(void)layoutUI{
//創(chuàng)建多個圖片控件用于顯示圖片
_imageViews=[NSMutableArray array];
for (int r=0; r<ROW_COUNT; r++) {
for (int c=0; c<COLUMN_COUNT; c++) {
UIImageView imageView=[[UIImageView alloc]initWithFrame:CGRectMake(cROW_WIDTH+(cCELL_SPACING), rROW_HEIGHT+(r*CELL_SPACING ), ROW_WIDTH, ROW_HEIGHT)];
imageView.contentMode=UIViewContentModeScaleAspectFit;
[self.view addSubview:imageView];
[_imageViews addObject:imageView];

    }
}

UIButton *button=[UIButton buttonWithType:UIButtonTypeRoundedRect];
button.frame=CGRectMake(50, 500, 220, 25);
[button setTitle:@"加載圖片" forState:UIControlStateNormal];
//添加方法
[button addTarget:self action:@selector(loadImageWithMultiThread) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];

//創(chuàng)建圖片鏈接
_imageNames=[NSMutableArray array];
for (int i=0; i<IMAGE_COUNT; i++) {
    [_imageNames addObject:[NSString stringWithFormat:@"http://images.cnblogs.com/cnblogs_com/kenshincui/613474/o_%i.jpg",i]];
}

//初始化鎖對象
_lock=[[NSLock alloc]init];

}

pragma mark 將圖片顯示到界面

-(void)updateImageWithData:(NSData *)data andIndex:(int )index{
UIImage *image=[UIImage imageWithData:data];
UIImageView *imageView= _imageViews[index];
imageView.image=image;
}

pragma mark 請求圖片數(shù)據(jù)

-(NSData *)requestData:(int )index{
NSData *data;
NSString *name;
//加鎖
[_lock lock];
if (_imageNames.count>0) {
name=[_imageNames lastObject];
[_imageNames removeObject:name];
}
//使用完解鎖
[_lock unlock];
if(name){
NSURL *url=[NSURL URLWithString:name];
data=[NSData dataWithContentsOfURL:url];
}
return data;
}

pragma mark 加載圖片

-(void)loadImage:(NSNumber *)index{
int i=[index integerValue];
//請求數(shù)據(jù)
NSData *data= [self requestData:i];
//更新UI界面,此處調(diào)用了GCD主線程隊(duì)列的方法
dispatch_queue_t mainQueue= dispatch_get_main_queue();
dispatch_sync(mainQueue, ^{
[self updateImageWithData:data andIndex:i];
});
}

pragma mark 多線程下載圖片

-(void)loadImageWithMultiThread{
int count=ROW_COUNT*COLUMN_COUNT;

dispatch_queue_t globalQueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//創(chuàng)建多個線程用于填充圖片
for (int i=0; i<count; ++i) {
    //異步執(zhí)行隊(duì)列任務(wù)
    dispatch_async(globalQueue, ^{
        [self loadImage:[NSNumber numberWithInt:i]];
    });
}

}
@end</pre>

運(yùn)行效果:

前面也說過使用同步鎖時(shí)如果一個線程A已經(jīng)加鎖,線程B就無法進(jìn)入渗蟹。那么B怎么知道是否資源已經(jīng)被其他線程鎖住呢洲押?可以通過tryLock方法,此方法會返回一個BOOL型的值圆凰,如果為YES說明獲取鎖成功杈帐,否則失敗。另外還有一個lockBeforeData:方法指定在某個時(shí)間內(nèi)獲取鎖专钉,同樣返回一個BOOL值挑童,如果在這個時(shí)間內(nèi)加鎖成功則返回YES,失敗則返回NO跃须。

<a name="t19"></a>@synchronized代碼塊

使用@synchronized解決線程同步問題相比較NSLock要簡單一些站叼,日常開發(fā)中也更推薦使用此方法。首先選擇一個對象作為同步對象(一般使用self)回怜,然后將”加鎖代碼”(爭奪資源的讀取大年、修改代碼)放到代碼塊中。@synchronized中的代碼執(zhí)行時(shí)先檢查同步對象是否被另一個線程占用玉雾,如果占用該線程就會處于等待狀態(tài)翔试,直到同步對象被釋放。下面的代碼演示了如何使用@synchronized進(jìn)行線程同步:

<pre name="code" class="code" style="margin: 0px; white-space: pre-wrap; word-wrap: break-word; padding: 0px 10px; list-style-type: none; list-style-image: none; font-family: 'Courier New', Courier, monospace;">-(NSData *)requestData:(int )index{
NSData *data;
NSString *name;
//線程同步
@synchronized(self){
if (_imageNames.count>0) {
name=[_imageNames lastObject];
[NSThread sleepForTimeInterval:0.001f];
[_imageNames removeObject:name];
}
}
if(name){
NSURL *url=[NSURL URLWithString:name];
data=[NSData dataWithContentsOfURL:url];
}
return data;
}</pre>

<a name="t20"></a>擴(kuò)展--使用GCD解決資源搶占問題

在GCD中提供了一種信號機(jī)制复旬,也可以解決資源搶占問題(和同步鎖的機(jī)制并不一樣)垦缅。GCD中信號量是dispatch_semaphore_t類型,支持信號通知和信號等待驹碍。每當(dāng)發(fā)送一個信號通知壁涎,則信號量+1;每當(dāng)發(fā)送一個等待信號時(shí)信號量-1,志秃;如果信號量為0則信號會處于等待狀態(tài)怔球,直到信號量大于0開始執(zhí)行。根據(jù)這個原理我們可以初始化一個信號量變量浮还,默認(rèn)信號量設(shè)置為1竟坛,每當(dāng)有線程進(jìn)入“加鎖代碼”之后就調(diào)用信號等待命令(此時(shí)信號量為0)開始等待,此時(shí)其他線程無法進(jìn)入钧舌,執(zhí)行完后發(fā)送信號通知(此時(shí)信號量為1)担汤,其他線程開始進(jìn)入執(zhí)行,如此一來就達(dá)到了線程同步目的洼冻。

<pre name="code" class="code" style="margin: 0px; white-space: pre-wrap; word-wrap: break-word; padding: 0px 10px; list-style-type: none; list-style-image: none; font-family: 'Courier New', Courier, monospace;">//
// GCD實(shí)現(xiàn)多線程--消息信號
// MultiThread
//
// Created by Kenshin Cui on 14-3-22.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

import "KCMainViewController.h"

import "KCImageData.h"

define ROW_COUNT 5

define COLUMN_COUNT 3

define ROW_HEIGHT 100

define ROW_WIDTH ROW_HEIGHT

define CELL_SPACING 10

define IMAGE_COUNT 9

@interface KCMainViewController (){
NSMutableArray *_imageViews;
NSLock *_lock;
dispatch_semaphore_t _semaphore;//定義一個信號量
}

@end

@implementation KCMainViewController

  • (void)viewDidLoad {
    [super viewDidLoad];

    [self layoutUI];
    }

pragma mark 界面布局

-(void)layoutUI{
//創(chuàng)建多個圖片控件用于顯示圖片
_imageViews=[NSMutableArray array];
for (int r=0; r<ROW_COUNT; r++) {
for (int c=0; c<COLUMN_COUNT; c++) {
UIImageView imageView=[[UIImageView alloc]initWithFrame:CGRectMake(cROW_WIDTH+(cCELL_SPACING), rROW_HEIGHT+(r*CELL_SPACING ), ROW_WIDTH, ROW_HEIGHT)];
imageView.contentMode=UIViewContentModeScaleAspectFit;
[self.view addSubview:imageView];
[_imageViews addObject:imageView];

    }
}

UIButton *button=[UIButton buttonWithType:UIButtonTypeRoundedRect];
button.frame=CGRectMake(50, 500, 220, 25);
[button setTitle:@"加載圖片" forState:UIControlStateNormal];
//添加方法
[button addTarget:self action:@selector(loadImageWithMultiThread) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];

//創(chuàng)建圖片鏈接
_imageNames=[NSMutableArray array];
for (int i=0; i<IMAGE_COUNT; i++) {
    [_imageNames addObject:[NSString stringWithFormat:@"http://images.cnblogs.com/cnblogs_com/kenshincui/613474/o_%i.jpg",i]];
}

/*初始化信號量
 參數(shù)是信號量初始值
 */
_semaphore=dispatch_semaphore_create(1);

}

pragma mark 將圖片顯示到界面

-(void)updateImageWithData:(NSData *)data andIndex:(int )index{
UIImage *image=[UIImage imageWithData:data];
UIImageView *imageView= _imageViews[index];
imageView.image=image;
}

pragma mark 請求圖片數(shù)據(jù)

-(NSData *)requestData:(int )index{
NSData *data;
NSString *name;

/*信號等待
 第二個參數(shù):等待時(shí)間
 */
dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
if (_imageNames.count>0) {
    name=[_imageNames lastObject];
    [_imageNames removeObject:name];
}
//信號通知
dispatch_semaphore_signal(_semaphore);

if(name){
    NSURL *url=[NSURL URLWithString:name];
    data=[NSData dataWithContentsOfURL:url];
}

return data;

}

pragma mark 加載圖片

-(void)loadImage:(NSNumber *)index{
int i=[index integerValue];
//請求數(shù)據(jù)
NSData *data= [self requestData:i];
//更新UI界面,此處調(diào)用了GCD主線程隊(duì)列的方法
dispatch_queue_t mainQueue= dispatch_get_main_queue();
dispatch_sync(mainQueue, ^{
[self updateImageWithData:data andIndex:i];
});
}

pragma mark 多線程下載圖片

-(void)loadImageWithMultiThread{
int count=ROW_COUNT*COLUMN_COUNT;
// dispatch_queue_t globalQueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

//這里創(chuàng)建一個并發(fā)隊(duì)列(使用全局并發(fā)隊(duì)列也可以)
dispatch_queue_t queue=dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);

for (int i=0; i<count; i++) {
    dispatch_async(queue, ^{
        [self loadImage:[NSNumber numberWithInt:i]];
    });
}

}

@end</pre>

運(yùn)行效果與前面使用同步鎖是一樣的崭歧。

<a name="t21"></a>擴(kuò)展--控制線程通信

由于線程的調(diào)度是透明的,程序有時(shí)候很難對它進(jìn)行有效的控制撞牢,為了解決這個問題iOS提供了NSCondition來控制線程通信(同前面GCD的信號機(jī)制類似)率碾。NSCondition實(shí)現(xiàn)了NSLocking協(xié)議叔营,所以它本身也有l(wèi)ock和unlock方法,因此也可以將它作為NSLock解決線程同步問題播掷,此時(shí)使用方法跟NSLock沒有區(qū)別审编,只要在線程開始時(shí)加鎖,取得資源后釋放鎖即可歧匈,這部分內(nèi)容比較簡單在此不再演示垒酬。當(dāng)然,單純解決線程同步問題不是NSCondition設(shè)計(jì)的主要目的件炉,NSCondition更重要的是解決線程之間的調(diào)度關(guān)系(當(dāng)然勘究,這個過程中也必須先加鎖、解鎖)斟冕。NSCondition可以調(diào)用wati方法控制某個線程處于等待狀態(tài)口糕,直到其他線程調(diào)用signal(此方法喚醒一個線程,如果有多個線程在等待則任意喚醒一個)或者broadcast(此方法會喚醒所有等待線程)方法喚醒該線程才能繼續(xù)磕蛇。

假設(shè)當(dāng)前imageNames沒有任何圖片景描,而整個界面能夠加載15張圖片(每張都不能重復(fù)),現(xiàn)在創(chuàng)建15個線程分別從imageNames中取圖片加載到界面中秀撇。由于imageNames中沒有任何圖片超棺,那么15個線程都處于等待狀態(tài),只有當(dāng)調(diào)用圖片創(chuàng)建方法往imageNames中添加圖片后(每次創(chuàng)建一個)并且喚醒其他線程(這里只喚醒一個線程)才能繼續(xù)執(zhí)行加載圖片呵燕。如此棠绘,每次創(chuàng)建一個圖片就會喚醒一個線程去加載,這個過程其實(shí)就是一個典型的生產(chǎn)者-消費(fèi)者模式再扭。下面通過NSCondition實(shí)現(xiàn)這個流程的控制:

KCMainViewController.h

<pre name="code" class="code" style="margin: 0px; white-space: pre-wrap; word-wrap: break-word; padding: 0px 10px; list-style-type: none; list-style-image: none; font-family: 'Courier New', Courier, monospace;">//
// KCMainViewController.h
// MultiThread
//
// Created by Kenshin Cui on 14-3-22.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

import <UIKit/UIKit.h>

@interface KCMainViewController : UIViewController

pragma mark 圖片資源存儲容器

@property (atomic,strong) NSMutableArray *imageNames;

pragma mark 當(dāng)前加載的圖片索引(圖片鏈接地址連續(xù))

@property (atomic,assign) int currentIndex;

@end</pre>

KCMainViewController.m

<pre name="code" class="code" style="margin: 0px; white-space: pre-wrap; word-wrap: break-word; padding: 0px 10px; list-style-type: none; list-style-image: none; font-family: 'Courier New', Courier, monospace;">//
// 線程控制
// MultiThread
//
// Created by Kenshin Cui on 14-3-22.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

import "KCMainViewController.h"

import "KCImageData.h"

define ROW_COUNT 5

define COLUMN_COUNT 3

define ROW_HEIGHT 100

define ROW_WIDTH ROW_HEIGHT

define CELL_SPACING 10

define IMAGE_COUNT 9

@interface KCMainViewController (){
NSMutableArray *_imageViews;
NSCondition *_condition;
}

@end

@implementation KCMainViewController

pragma mark - 事件

  • (void)viewDidLoad {
    [super viewDidLoad];

    [self layoutUI];
    }

pragma mark - 內(nèi)部私有方法

pragma mark 界面布局

-(void)layoutUI{
//創(chuàng)建多個圖片控件用于顯示圖片
_imageViews=[NSMutableArray array];
for (int r=0; r<ROW_COUNT; r++) {
for (int c=0; c<COLUMN_COUNT; c++) {
UIImageView imageView=[[UIImageView alloc]initWithFrame:CGRectMake(cROW_WIDTH+(cCELL_SPACING), rROW_HEIGHT+(r*CELL_SPACING ), ROW_WIDTH, ROW_HEIGHT)];
imageView.contentMode=UIViewContentModeScaleAspectFit;
[self.view addSubview:imageView];
[_imageViews addObject:imageView];

    }
}

UIButton *btnLoad=[UIButton buttonWithType:UIButtonTypeRoundedRect];
btnLoad.frame=CGRectMake(50, 500, 100, 25);
[btnLoad setTitle:@"加載圖片" forState:UIControlStateNormal];
[btnLoad addTarget:self action:@selector(loadImageWithMultiThread) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btnLoad];

UIButton *btnCreate=[UIButton buttonWithType:UIButtonTypeRoundedRect];
btnCreate.frame=CGRectMake(160, 500, 100, 25);
[btnCreate setTitle:@"創(chuàng)建圖片" forState:UIControlStateNormal];
[btnCreate addTarget:self action:@selector(createImageWithMultiThread) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btnCreate];

//創(chuàng)建圖片鏈接
_imageNames=[NSMutableArray array];

//初始化鎖對象
_condition=[[NSCondition alloc]init];

_currentIndex=0;

}

pragma mark 創(chuàng)建圖片

-(void)createImageName{
[_condition lock];
//如果當(dāng)前已經(jīng)有圖片了則不再創(chuàng)建氧苍,線程處于等待狀態(tài)
if (_imageNames.count>0) {
NSLog(@"createImageName wait, current:%i",_currentIndex);
[_condition wait];
}else{
NSLog(@"createImageName work, current:%i",_currentIndex);
//生產(chǎn)者,每次生產(chǎn)1張圖片
[imageNames addObject:[NSString stringWithFormat:@"http://images.cnblogs.com/cnblogs_com/kenshincui/613474/o%i.jpg",_currentIndex++]];

    //創(chuàng)建完圖片則發(fā)出信號喚醒其他等待線程
    [_condition signal];
}
[_condition unlock];

}

pragma mark 加載圖片并將圖片顯示到界面

-(void)loadAnUpdateImageWithIndex:(int )index{
//請求數(shù)據(jù)
NSData *data= [self requestData:index];
//更新UI界面,此處調(diào)用了GCD主線程隊(duì)列的方法
dispatch_queue_t mainQueue= dispatch_get_main_queue();
dispatch_sync(mainQueue, ^{
UIImage *image=[UIImage imageWithData:data];
UIImageView *imageView= _imageViews[index];
imageView.image=image;
});
}

pragma mark 請求圖片數(shù)據(jù)

-(NSData *)requestData:(int )index{
NSData *data;
NSString *name;
name=[_imageNames lastObject];
[_imageNames removeObject:name];
if(name){
NSURL *url=[NSURL URLWithString:name];
data=[NSData dataWithContentsOfURL:url];
}
return data;
}

pragma mark 加載圖片

-(void)loadImage:(NSNumber *)index{
int i=(int)[index integerValue];
//加鎖
[_condition lock];
//如果當(dāng)前有圖片資源則加載泛范,否則等待
if (_imageNames.count>0) {
NSLog(@"loadImage work,index is %i",i);
[self loadAnUpdateImageWithIndex:i];
[_condition broadcast];
}else{
NSLog(@"loadImage wait,index is %i",i);
NSLog(@"%@",[NSThread currentThread]);
//線程等待
[_condition wait];
NSLog(@"loadImage resore,index is %i",i);
//一旦創(chuàng)建完圖片立即加載
[self loadAnUpdateImageWithIndex:i];
}
//解鎖
[_condition unlock];
}

pragma mark - UI調(diào)用方法

pragma mark 異步創(chuàng)建一張圖片鏈接

-(void)createImageWithMultiThread{
dispatch_queue_t globalQueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//創(chuàng)建圖片鏈接
dispatch_async(globalQueue, ^{
[self createImageName];
});
}

pragma mark 多線程下載圖片

-(void)loadImageWithMultiThread{
int count=ROW_COUNT*COLUMN_COUNT;
dispatch_queue_t globalQueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

for (int i=0; i<count; ++i) {
    //加載圖片
    dispatch_async(globalQueue, ^{
        [self loadImage:[NSNumber numberWithInt:i]];
    });
}

}
@end</pre>

運(yùn)行效果:

在上面的代碼中loadImage:方法是消費(fèi)者让虐,當(dāng)在界面中點(diǎn)擊“加載圖片”后就創(chuàng)建了15個消費(fèi)者線程。在這個過程中每個線程進(jìn)入圖片加載方法之后都會先加鎖罢荡,加鎖之后其他進(jìn)程是無法進(jìn)入“加鎖代碼”的赡突。但是第一個線程進(jìn)入“加鎖代碼”后去加載圖片卻發(fā)現(xiàn)當(dāng)前并沒有任何圖片,因此它只能等待柠傍。一旦調(diào)用了NSCondition的wait方法后其他線程就可以繼續(xù)進(jìn)入“加鎖代碼”(注意麸俘,這一點(diǎn)和前面說的NSLock辩稽、@synchronized等是不同的惧笛,使用NSLock、@synchronized等進(jìn)行加鎖后無論什么情況下逞泄,只要沒有解鎖其他線程就無法進(jìn)入“加鎖代碼”)患整,同時(shí)第一個線程處于等待隊(duì)列中(此時(shí)并未解鎖)拜效。第二個線程進(jìn)來之后同第一線程一樣,發(fā)現(xiàn)沒有圖片就進(jìn)入等待狀態(tài)各谚,然后第三個線程進(jìn)入紧憾。。昌渤。如此反復(fù)赴穗,直到第十五個線程也處于等待。此時(shí)點(diǎn)擊“創(chuàng)建圖片”后會執(zhí)行createImageName方法膀息,這是一個生產(chǎn)者般眉,它會創(chuàng)建一個圖片鏈接放到imageNames中,然后通過調(diào)用NSCondition的signal方法就會在條件等待隊(duì)列中選擇一個線程(該線程會任意選取潜支,假設(shè)為線程A)開啟甸赃,那么此時(shí)這個線程就會繼續(xù)執(zhí)行。在上面代碼中冗酿,wati方法之后會繼續(xù)執(zhí)行圖片加載方法埠对,那么此時(shí)線程A啟動之后繼續(xù)執(zhí)行圖片加載方法,當(dāng)然此時(shí)可以成功加載圖片裁替。加載完圖片之后線程A就會釋放鎖项玛,整個線程任務(wù)完成。此時(shí)再次點(diǎn)擊”創(chuàng)建圖片“按鈕重復(fù)前面的步驟加載其他圖片胯究。

為了說明上面的過程稍计,這里以一個流程圖的進(jìn)行說明,流程圖藍(lán)色部分代表15個加載圖片的線程裕循,綠色部分表示創(chuàng)建圖片資源線程臣嚣。

<a name="t22"></a>iOS中的其他鎖

在iOS開發(fā)中,除了同步鎖有時(shí)候還會用到一些其他鎖類型剥哑,在此簡單介紹一下:

NSRecursiveLock :遞歸鎖硅则,有時(shí)候“加鎖代碼”中存在遞歸調(diào)用,遞歸開始前加鎖株婴,遞歸調(diào)用開始后會重復(fù)執(zhí)行此方法以至于反復(fù)執(zhí)行加鎖代碼最終造成死鎖怎虫,這個時(shí)候可以使用遞歸鎖來解決。使用遞歸鎖可以在一個線程中反復(fù)獲取鎖而不造成死鎖困介,這個過程中會記錄獲取鎖和釋放鎖的次數(shù)大审,只有最后兩者平衡鎖才被最終釋放。

NSDistributedLock:分布鎖座哩,它本身是一個互斥鎖徒扶,基于文件方式實(shí)現(xiàn)鎖機(jī)制,可以跨進(jìn)程訪問根穷。

pthread_mutex_t:同步鎖,基于C語言的同步鎖機(jī)制,使用方法與其他同步鎖機(jī)制類似滔金。

提示:在開發(fā)過程中除非必須用鎖,否則應(yīng)該盡可能不使用鎖惫周,因?yàn)槎嗑€程開發(fā)本身就是為了提高程序執(zhí)行順序,而同步鎖本身就只能一個進(jìn)程執(zhí)行康栈,這樣不免降低執(zhí)行效率递递。

<a name="t23"></a>總結(jié)

1>無論使用哪種方法進(jìn)行多線程開發(fā),每個線程啟動后并不一定立即執(zhí)行相應(yīng)的操作啥么,具體什么時(shí)候由系統(tǒng)調(diào)度(CPU空閑時(shí)就會執(zhí)行)漾狼。

2>更新UI應(yīng)該在主線程(UI線程)中進(jìn)行,并且推薦使用同步調(diào)用饥臂,常用的方法如下:

    • (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait (或者-(void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL) wait;方法傳遞主線程[NSThread mainThread])
  • [NSOperationQueue mainQueue] addOperationWithBlock:
  • dispatch_sync(dispatch_get_main_queue(), ^{})

3>NSThread適合輕量級多線程開發(fā)逊躁,控制線程順序比較難,同時(shí)線程總數(shù)無法控制(每次創(chuàng)建并不能重用之前的線程隅熙,只能創(chuàng)建一個新的線程)稽煤。

4>對于簡單的多線程開發(fā)建議使用NSObject的擴(kuò)展方法完成,而不必使用NSThread囚戚。

5>可以使用NSThread的currentThread方法取得當(dāng)前線程酵熙,使用 sleepForTimeInterval:方法讓當(dāng)前線程休眠。

6>NSOperation進(jìn)行多線程開發(fā)可以控制線程總數(shù)及線程依賴關(guān)系驰坊。

7>創(chuàng)建一個NSOperation不應(yīng)該直接調(diào)用start方法(如果直接start則會在主線程中調(diào)用)而是應(yīng)該放到NSOperationQueue中啟動匾二。

8>相比NSInvocationOperation推薦使用NSBlockOperation,代碼簡單拳芙,同時(shí)由于閉包性使它沒有傳參問題察藐。

9>NSOperation是對GCD面向?qū)ο蟮腛bjC封裝,但是相比GCD基于C語言開發(fā)舟扎,效率卻更高分飞,建議如果任務(wù)之間有依賴關(guān)系或者想要監(jiān)聽任務(wù)完成狀態(tài)的情況下優(yōu)先選擇NSOperation否則使用GCD。

10>在GCD中串行隊(duì)列中的任務(wù)被安排到一個單一線程執(zhí)行(不是主線程)睹限,可以方便地控制執(zhí)行順序譬猫;并發(fā)隊(duì)列在多個線程中執(zhí)行(前提是使用異步方法),順序控制相對復(fù)雜羡疗,但是更高效染服。

11>在GDC中一個操作是多線程執(zhí)行還是單線程執(zhí)行取決于當(dāng)前隊(duì)列類型和執(zhí)行方法,只有隊(duì)列類型為并行隊(duì)列并且使用異步方法執(zhí)行時(shí)才能在多個線程中執(zhí)行(如果是并行隊(duì)列使用同步方法調(diào)用則會在主線程中執(zhí)行)叨恨。

12>相比使用NSLock柳刮,@synchronized更加簡單,推薦使用后者。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末诚亚,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子午乓,更是在濱河造成了極大的恐慌站宗,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件益愈,死亡現(xiàn)場離奇詭異梢灭,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)蒸其,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進(jìn)店門敏释,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人摸袁,你說我怎么就攤上這事钥顽。” “怎么了靠汁?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵蜂大,是天一觀的道長。 經(jīng)常有香客問我蝶怔,道長奶浦,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任踢星,我火速辦了婚禮澳叉,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘沐悦。我一直安慰自己成洗,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布藏否。 她就那樣靜靜地躺著泌枪,像睡著了一般。 火紅的嫁衣襯著肌膚如雪秕岛。 梳的紋絲不亂的頭發(fā)上碌燕,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天,我揣著相機(jī)與錄音继薛,去河邊找鬼修壕。 笑死,一個胖子當(dāng)著我的面吹牛遏考,可吹牛的內(nèi)容都是我干的慈鸠。 我是一名探鬼主播,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼灌具,長吁一口氣:“原來是場噩夢啊……” “哼青团!你這毒婦竟也來了譬巫?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤督笆,失蹤者是張志新(化名)和其女友劉穎芦昔,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體娃肿,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡咕缎,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了料扰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片凭豪。...
    茶點(diǎn)故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖晒杈,靈堂內(nèi)的尸體忽然破棺而出嫂伞,到底是詐尸還是另有隱情,我是刑警寧澤拯钻,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布末早,位于F島的核電站,受9級特大地震影響说庭,放射性物質(zhì)發(fā)生泄漏然磷。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一刊驴、第九天 我趴在偏房一處隱蔽的房頂上張望姿搜。 院中可真熱鬧,春花似錦捆憎、人聲如沸舅柜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽致份。三九已至,卻和暖如春础拨,著一層夾襖步出監(jiān)牢的瞬間氮块,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工诡宗, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留滔蝉,地道東北人。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓塔沃,卻偏偏與公主長得像蝠引,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評論 2 354

推薦閱讀更多精彩內(nèi)容

  • NSThread 第一種:通過NSThread的對象方法 NSThread *thread = [[NSThrea...
    攻城獅GG閱讀 797評論 0 3
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,097評論 1 32
  • 吃素的训貌,就都是良善之輩么? 我曾經(jīng)任職的公司附近有一個餐館融蹂,裝潢精致,菜色卻一般弄企,沒有什么特色超燃,因著用餐環(huán)境看著高...
    東籬書院閱讀 1,870評論 8 17
  • 失落的時(shí)候好想化身成風(fēng),成為他們的一份子拘领。想隱藏的時(shí)候無形意乓,想沉默的時(shí)候無聲,想放空的時(shí)候無心约素。 無力支撐届良,艱難挪...
    feiyuguangyi閱讀 297評論 0 1
  • 自動保存 打開設(shè)置面板:Code→首選項(xiàng)(Preferences)→設(shè)置(settings) 在搜索框中輸入“au...
    Coder_老王閱讀 6,855評論 0 2