日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

OC的多线程

發(fā)布時(shí)間:2025/7/14 编程问答 21 豆豆
生活随笔 收集整理的這篇文章主要介紹了 OC的多线程 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

轉(zhuǎn)發(fā)微博:http://blog.csdn.net/shenjie12345678/article/details/44152605

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

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

?

在iOS中每個(gè)進(jìn)程啟動后都會建立一個(gè)主線程(UI線程),這個(gè)線程是其他線程的父線程。由于在iOS中除了主線程,其他子線程是獨(dú)立于Cocoa Touch的,所以只有主線程可以更新UI界面(新版iOS中,使用其他線程更新UI可能也能成功,但是不推薦)。iOS中多線程使用并不復(fù)雜,關(guān)鍵是如何控制好各個(gè)線程的執(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)用,因此在文章中也會對比講解。

?

?

?

1.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)建一個(gè)線程對象,然后調(diào)用start方法啟動線程。

解決線程阻塞問題

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

// // 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的分類方法,每個(gè)NSObject對象都有此方法,它調(diào)用的selector方法是當(dāng)前調(diào)用控件的方法,例如使用UIImageView調(diào)用的時(shí)候selector就是UIImageView的方法Object:代表調(diào)用方法的參數(shù),不過只能傳遞一個(gè)參數(shù)(如果有多個(gè)參數(shù)請使用對象進(jìn)行封裝)waitUntilDone:是否線程任務(wù)完成執(zhí)行*/[self performSelectorOnMainThread:@selector(updateImage:) withObject:data waitUntilDone:YES]; }#pragma mark 多線程下載圖片 -(void)loadImageWithMultiThread{//方法1:使用對象方法//創(chuàng)建一個(gè)線程,第一個(gè)參數(shù)是請求的操作,第二個(gè)參數(shù)是操作方法的參數(shù) // NSThread *thread=[[NSThread alloc]initWithTarget:self selector:@selector(loadImage) object:nil]; // //啟動一個(gè)線程,注意啟動一個(gè)線程并非就一定立即執(zhí)行,而是處于就緒狀態(tài),當(dāng)系統(tǒng)調(diào)度時(shí)才真正執(zhí)行 // [thread start];//方法2:使用類方法[NSThread detachNewThreadSelector:@selector(loadImage) toTarget:self withObject:nil]; } @end
程序比較簡單,但是需要注意執(zhí)行步驟:當(dāng)點(diǎn)擊了“加載圖片”按鈕后啟動一個(gè)新的線程,這個(gè)線程在演示中大概用了5s左右,在這5s內(nèi)UI線程是不會阻塞的,用戶可以進(jìn)行其他操作,大約5s之后圖片下載完成,此時(shí)調(diào)用UI線程將圖片顯示到界面中(這個(gè)過程瞬間完成)。另外前面也提到過,更新UI的時(shí)候使用UI線程,這里調(diào)用了NSObject的分類擴(kuò)展方法,調(diào)用UI線程完成更新。

?

多個(gè)線程并發(fā)

上面這個(gè)演示并沒有演示多個(gè)子線程操作之間的關(guān)系,現(xiàn)在不妨在界面中多加載幾張圖片,每個(gè)圖片都來自遠(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方法都只能傳一個(gè)參數(shù),由于更新圖片需要傳遞UIImageView的索引和圖片數(shù)據(jù),因此這里不妨定義一個(gè)類保存圖片索引和圖片數(shù)據(jù)以供后面使用。

KCImageData.h

// // 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

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

KCMainViewController.m

// // 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)建多個(gè)圖片控件用于顯示圖片_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(c*ROW_WIDTH+(c*CELL_SPACING), r*ROW_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)前操作線程N(yùn)SLog(@"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)建多個(gè)線程用于填充圖片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

通過NSThread的currentThread可以取得當(dāng)前操作的線程,其中會記錄線程名稱name和編號number,需要注意主線程編號永遠(yuǎn)為1。多個(gè)線程雖然按順序啟動,但是實(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),圖片并未按順序加載,原因有兩個(gè):第一,每個(gè)線程的實(shí)際執(zhí)行順序并不一定按順序執(zhí)行(雖然是按順序啟動);第二,每個(gè)線程執(zhí)行時(shí)實(shí)際網(wǎng)絡(luò)狀況很可能不一致。當(dāng)然網(wǎng)絡(luò)問題無法改變,只能盡可能讓網(wǎng)速更快,但是可以改變線程的優(yōu)先級,讓15個(gè)線程優(yōu)先執(zhí)行某個(gè)線程。線程優(yōu)先級范圍為0~1,值越大優(yōu)先級越高,每個(gè)線程的優(yōu)先級默認(rèn)為0.5。修改圖片下載方法如下,改變最后一張圖片加載的優(yōu)先級,這樣可以提高它被優(yōu)先加載的幾率,但是它也未必就第一個(gè)加載。因?yàn)槭紫绕渌€程是先啟動的,其次網(wǎng)絡(luò)狀況我們沒辦法修改:

?

-(void)loadImageWithMultiThread{NSMutableArray *threads=[NSMutableArray array];int count=ROW_COUNT*COLUMN_COUNT;//創(chuàng)建多個(gè)線程用于填充圖片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];} }

線程狀態(tài)

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

-(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;} }

在這里讓其他線程休眠2秒,此時(shí)你就會看到最后一張圖片總是第一個(gè)加載(除非網(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方法,這是一個(gè)靜態(tài)方法,調(diào)用該方法可以退出當(dāng)前線程。

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

// // 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)建多個(gè)圖片空間用于顯示圖片_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(c*ROW_WIDTH+(c*CELL_SPACING), r*ROW_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)建多個(gè)線程用于填充圖片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];}} }

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

擴(kuò)展--NSObject分類擴(kuò)展方法

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

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

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

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

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

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

?

?

NSOperation

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

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

NSInvocationOperation

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

-(void)loadImageWithMultiThread{/*創(chuàng)建一個(gè)調(diào)用操作object:調(diào)用方法參數(shù)*/NSInvocationOperation *invocationOperation=[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(loadImage) object:nil];//創(chuàng)建完NSInvocationOperation對象并不會調(diào)用,它由一個(gè)start方法啟動操作,但是注意如果直接調(diào)用start方法,則此操作會在主線程中調(diào)用,一般不會這么操作,而是添加到NSOperationQueue中 // [invocationOperation start];//創(chuàng)建操作隊(duì)列NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];//注意添加到操作隊(duì)后,隊(duì)列會開啟一個(gè)線程執(zhí)行此操作[operationQueue addOperation:invocationOperation]; }

NSBlockOperation

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

// // 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)建多個(gè)圖片控件用于顯示圖片_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(c*ROW_WIDTH+(c*CELL_SPACING), r*ROW_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)建多個(gè)線程用于填充圖片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

?

?

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

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

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

    -(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)建多個(gè)線程用于填充圖片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];}//將最后一個(gè)圖片的加載操作加入線程隊(duì)列[operationQueue addOperation:lastBlockOperation]; }

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

    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中也有一個(gè)類似于NSOperationQueue的隊(duì)列,GCD統(tǒng)一管理整個(gè)隊(duì)列中的任務(wù)。但是GCD中的隊(duì)列分為并行隊(duì)列和串行隊(duì)列兩類:

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

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

    串行隊(duì)列

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

    // // 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)建多個(gè)圖片控件用于顯示圖片_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(c*ROW_WIDTH+(c*CELL_SPACING), r*ROW_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)樗麄冊谝粋€(gè)線程中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)建一個(gè)串行隊(duì)列第一個(gè)參數(shù):隊(duì)列名稱第二個(gè)參數(shù):隊(duì)列類型*/dispatch_queue_t serialQueue=dispatch_queue_create("myThreadQueue1", DISPATCH_QUEUE_SERIAL);//注意queue對象不是指針類型//創(chuàng)建多個(gè)線程用于填充圖片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

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

    并發(fā)隊(duì)列

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

    -(void)loadImageWithMultiThread{int count=ROW_COUNT*COLUMN_COUNT;/*取得全局隊(duì)列第一個(gè)參數(shù):線程優(yōu)先級第二個(gè)參數(shù):標(biāo)記參數(shù),目前沒有用,一般傳入0*/dispatch_queue_t globalQueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);//創(chuàng)建多個(gè)線程用于填充圖片for (int i=0; i<count; ++i) {//異步執(zhí)行隊(duì)列任務(wù)dispatch_async(globalQueue, ^{[self loadImage:[NSNumber numberWithInt:i]];});} }
    細(xì)心的朋友肯定會思考,既然可以使用dispatch_async()異步調(diào)用方法,是不是還有同步方法,確實(shí)如此,在GCD中還有一個(gè)dispatch_sync()方法。假設(shè)將上面的代碼修改為同步調(diào)用,可以看到如下效果:


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

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

    其他任務(wù)執(zhí)行方法

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

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

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

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

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

    // // 線程同步 // 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)建多個(gè)圖片控件用于顯示圖片_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(c*ROW_WIDTH+(c*CELL_SPACING), r*ROW_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)建多個(gè)線程用于填充圖片for (int i=0; i<count; ++i) {//異步執(zhí)行隊(duì)列任務(wù)dispatch_async(globalQueue, ^{[self loadImage:[NSNumber numberWithInt:i]];});}} @end

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

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

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

    NSLock

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

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

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

    KCMainViewController.h

    // // 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

    KCMainViewController.m

    // // 線程同步 // 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)建多個(gè)圖片控件用于顯示圖片_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(c*ROW_WIDTH+(c*CELL_SPACING), r*ROW_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)建多個(gè)線程用于填充圖片for (int i=0; i<count; ++i) {//異步執(zhí)行隊(duì)列任務(wù)dispatch_async(globalQueue, ^{[self loadImage:[NSNumber numberWithInt:i]];});}} @end

    運(yùn)行效果:

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

    @synchronized代碼塊

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

    -(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; }

    擴(kuò)展--使用GCD解決資源搶占問題

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

    // // 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;//定義一個(gè)信號量 }@end@implementation KCMainViewController- (void)viewDidLoad {[super viewDidLoad];[self layoutUI]; }#pragma mark 界面布局 -(void)layoutUI{//創(chuàng)建多個(gè)圖片控件用于顯示圖片_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(c*ROW_WIDTH+(c*CELL_SPACING), r*ROW_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;/*信號等待第二個(gè)參數(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)建一個(gè)并發(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

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

    擴(kuò)展--控制線程通信

    由于線程的調(diào)度是透明的,程序有時(shí)候很難對它進(jìn)行有效的控制,為了解決這個(gè)問題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)然,這個(gè)過程中也必須先加鎖、解鎖)。NSCondition可以調(diào)用wati方法控制某個(gè)線程處于等待狀態(tài),直到其他線程調(diào)用signal(此方法喚醒一個(gè)線程,如果有多個(gè)線程在等待則任意喚醒一個(gè))或者broadcast(此方法會喚醒所有等待線程)方法喚醒該線程才能繼續(xù)。

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

    KCMainViewController.h

    // // 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

    KCMainViewController.m

    // // 線程控制 // 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)建多個(gè)圖片控件用于顯示圖片_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(c*ROW_WIDTH+(c*CELL_SPACING), r*ROW_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

    運(yùn)行效果:

    在上面的代碼中loadImage:方法是消費(fèi)者,當(dāng)在界面中點(diǎn)擊“加載圖片”后就創(chuàng)建了15個(gè)消費(fèi)者線程。在這個(gè)過程中每個(gè)線程進(jìn)入圖片加載方法之后都會先加鎖,加鎖之后其他進(jìn)程是無法進(jìn)入“加鎖代碼”的。但是第一個(gè)線程進(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í)第一個(gè)線程處于等待隊(duì)列中(此時(shí)并未解鎖)。第二個(gè)線程進(jìn)來之后同第一線程一樣,發(fā)現(xiàn)沒有圖片就進(jìn)入等待狀態(tài),然后第三個(gè)線程進(jìn)入。。。如此反復(fù),直到第十五個(gè)線程也處于等待。此時(shí)點(diǎn)擊“創(chuàng)建圖片”后會執(zhí)行createImageName方法,這是一個(gè)生產(chǎn)者,它會創(chuàng)建一個(gè)圖片鏈接放到imageNames中,然后通過調(diào)用NSCondition的signal方法就會在條件等待隊(duì)列中選擇一個(gè)線程(該線程會任意選取,假設(shè)為線程A)開啟,那么此時(shí)這個(gè)線程就會繼續(xù)執(zhí)行。在上面代碼中,wati方法之后會繼續(xù)執(zhí)行圖片加載方法,那么此時(shí)線程A啟動之后繼續(xù)執(zhí)行圖片加載方法,當(dāng)然此時(shí)可以成功加載圖片。加載完圖片之后線程A就會釋放鎖,整個(gè)線程任務(wù)完成。此時(shí)再次點(diǎn)擊”創(chuàng)建圖片“按鈕重復(fù)前面的步驟加載其他圖片。

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

    ?

    iOS中的其他鎖

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

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

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

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

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

    總結(jié)

    1>無論使用哪種方法進(jìn)行多線程開發(fā),每個(gè)線程啟動后并不一定立即執(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)建一個(gè)新的線程)。

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

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

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

    7>創(chuàng)建一個(gè)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ù)被安排到一個(gè)單一線程執(zhí)行(不是主線程),可以方便地控制執(zhí)行順序;并發(fā)隊(duì)列在多個(gè)線程中執(zhí)行(前提是使用異步方法),順序控制相對復(fù)雜,但是更高效。

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

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




    轉(zhuǎn)載于:https://www.cnblogs.com/shiyong139565/p/8205926.html

    《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀

    總結(jié)

    以上是生活随笔為你收集整理的OC的多线程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。