iOS開發(fā)中常用的幾種多線程方案,簡單做個小結(jié),方便日后查閱。
- NSThead
- GCD
- NSOperation & NSOpeartionQueue
- Pthreads
這種方式不用介紹(我也不太會使用),一般ios開發(fā)里也用不上,這是在很多操作系統(tǒng)中都通用的。使用方法大概如下:
#import
創(chuàng)建線程并執(zhí)行任務(wù):
void *run(void *data){
for (int i = 0; i<1000; i++) {
NSLog(@"touchesBegan:%d-----%@", i, [NSThread currentThread]); }
return NULL;}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
// 創(chuàng)建線程 pthread_t myRestrict;
pthread_create(&myRestrict, NULL, run, NULL);
}
通過log可以看到:
2016-03-08 18:27:04.936 testPthread[4859:1637201] touchesBegan:0-----{number = 3, name = (null)}
2016-03-08 18:27:04.937 testPthread[4859:1637201] touchesBegan:1-----{number = 3, name = (null)}
2016-03-08 18:27:04.937 testPthread[4859:1637201] touchesBegan:2-----{number = 3, name = (null)}
2016-03-08 18:27:04.937 testPthread[4859:1637201] touchesBegan:3-----{number = 3, name = (null)}
2016-03-08 18:27:04.937 testPthread[4859:1637201] touchesBegan:4-----{number = 3, name = (null)}
這種方式雖然創(chuàng)建了一個線程,但并沒有銷毀,我們需要手動管理線程的生命周期。隨意感受一下就好了。。。
NSThead
優(yōu)點(diǎn):NSThread 比其他幾個輕量級。
缺點(diǎn):需要自己管理線程的生命周期,線程同步。線程同步對數(shù)據(jù)的加鎖會有一定的系統(tǒng)開銷。
創(chuàng)建和啟動線程的3種方式:
1.先創(chuàng)建后啟動:
// 創(chuàng)建線程N(yùn)SThread*thread = [[NSThreadalloc] initWithTarget:selfselector:@selector(download:) object:@"http://b.png"];
thread.name=@"下載線程";
// 啟動線程(調(diào)用self的download方法)
[thread start];
selector :線程執(zhí)行的方法,這個selector只能有一個參數(shù),而且不能有返回值。
target :selector消息發(fā)送的對象
argument:傳輸給target的唯一參數(shù),也可以是nil
2.創(chuàng)建并自動啟動線程:
[NSThreaddetachNewThreadSelector:@selector(download:) toTarget:selfwithObject:@"http://a.jpg"];
3.使用NSObject方法創(chuàng)建線程并自動啟動:
[selfperformSelectorInBackground:@selector(download:)withObject:@"http://c.gif"];
其他常見方法:
//獲得當(dāng)前線程+(NSThread*)currentThread;
//獲得主線程+(NSThread*)mainThread;
//睡眠(暫停)線程+(void)sleepUntilDate:(NSDate*)date; +(void)sleepForTimeInterval:(NSTimeInterval)ti;
//設(shè)置和獲取線程名稱-(void)setName:(NSString*)n;
-(NSString*)name;
GCD
Grand Central Dispatch 簡稱(GCD)是蘋果公司開發(fā)的技術(shù),以優(yōu)化的應(yīng)用程序支持多核心處理器和其他的對稱多處理系統(tǒng)的系統(tǒng)。這建立在任務(wù)并行執(zhí)行的線程池模式的基礎(chǔ)上的。它首次發(fā)布在Mac OS X 10.6 ,iOS 4及以上也可用。GCD的工作原理是:讓程序平行排隊(duì)的特定任務(wù),根據(jù)可用的處理資源,安排他們在任何可用的處理器核心上執(zhí)行任務(wù)。它是蘋果為多核的并行運(yùn)算提出的解決方案,所以會自動合理地利用更多的CPU內(nèi)核(比如雙核、四核),最重要的是它會自動管理線程的生命周期(創(chuàng)建線程、調(diào)度任務(wù)、銷毀線程),完全不需要我們管理,我們只需要告訴干什么就行。
任務(wù)和隊(duì)列
任務(wù):
需要執(zhí)行什么操作,一個任務(wù)可以是一個函數(shù)(function)或者是一個block。任務(wù)有兩種執(zhí)行方式:同步執(zhí)行和異步執(zhí)行,它們區(qū)別在于是否會阻塞當(dāng)前線程,直到任務(wù)執(zhí)行完。如果是 同步(sync) 操作,它會阻塞當(dāng)前線程并等待 Block 中的任務(wù)執(zhí)行完畢,然后當(dāng)前線程才會繼續(xù)往下運(yùn)行。如果是 異步(async)操作,當(dāng)前線程會直接往下執(zhí)行,它不會阻塞當(dāng)前線程。
創(chuàng)建任務(wù)方式:
同步任務(wù):會阻塞當(dāng)前線程,并且不具備開啟新線程的能力。
dispatch_sync(<#queue#>, ^{//code here});
異步任務(wù):不會阻塞當(dāng)前線程,并且具有開啟新線程的能力。
dispatch_async(<#queue#>, ^{//code here});
隊(duì)列:
存放任務(wù)。有串行隊(duì)列和并行隊(duì)列兩種。
串行隊(duì)列:GCD中的FIFO隊(duì)列稱為dispatch queue,它可以保證先進(jìn)來的任務(wù)先得到執(zhí)行,然后再取下一個,一個接著一個執(zhí)行。
并行隊(duì)列:放到并行隊(duì)列的任務(wù)可以并發(fā)執(zhí)行。不過需要注意,GCD 會根據(jù)系統(tǒng)資源控制并行的數(shù)量,所以如果任務(wù)很多,它并不會讓所有任務(wù)同時執(zhí)行。
創(chuàng)建隊(duì)列方式
主隊(duì)列:這是一個特殊的串行隊(duì)列,主要用于刷新UI界面,處理UI控件的事件。如下:
dispatch_queue_t queue = dispatch_get_main_queue();
自建創(chuàng)建的隊(duì)列:自己可以創(chuàng)建串行隊(duì)列,也可以創(chuàng)建 并行隊(duì)列。它有兩個參數(shù):
第一個參數(shù):標(biāo)識符,用于debug的時候標(biāo)識唯一的隊(duì)列。
第二個參數(shù):用來表示創(chuàng)建的隊(duì)列是串行的還是并行的,傳入DISPATCH_QUEUE_SERIAL或NULL表示創(chuàng)建串行隊(duì)列。傳入DISPATCH_QUEUE_CONCURRENT表示創(chuàng)建并行隊(duì)列。如下:
//串行隊(duì)列dispatch_queue_tqueue=dispatch_queue_create("serial.testQueue",NULL);
//并發(fā)隊(duì)列dispatch_queue_tqueue= dispatch_queue_create("serial.testQueue", DISPATCH_QUEUE_SERIAL);
//并行隊(duì)列dispatch_queue_tqueue= dispatch_queue_create("concurrent.testQueue", DISPATCH_QUEUE_CONCURRENT);```
**全局并發(fā)隊(duì)列:**可以讓任務(wù)并發(fā)執(zhí)行,只要是并行任務(wù)一般都加入到這個隊(duì)列中。
`dispatch_queue_tqueue= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);`
下面的例子能更好的理解同步和異步和各種隊(duì)列的使用:
例1:
```objc
//sync -- 主隊(duì)列(不能用---會卡死)
-(void)testSyncMainQueue{
NSLog(@"download之前----%@",[NSThreadcurrentThread]);
// 1.主隊(duì)列(添加到主隊(duì)列中的任務(wù),都會自動放到主線程中去執(zhí)行)
dispatch_queue_tqueue = dispatch_get_main_queue();
// 2.異步執(zhí)行
dispatch_sync(queue, ^
{NSLog(@"-----download1---%@", [NSThreadcurrentThread]); });
dispatch_sync(queue, ^{
NSLog(@"-----download2---%@", [NSThreadcurrentThread]); });
NSLog(@"download之后------");
}```
打印結(jié)果:
>2016-03-09 10:37:47.450 testGCD[5725:2260551] download之前----{number = 1, name = main}
只打印了第一句后主線程就被卡死了,什么界面操作都做不了。這是因?yàn)閐ispatch_sync同步任務(wù)會阻塞當(dāng)前的主線程,然后把block中的任務(wù)放到主隊(duì)列queue當(dāng)中,可是添加到主隊(duì)列中的任務(wù),都會自動放到主線程中去執(zhí)行,但是主線程已被阻塞,所以block中download image無法執(zhí)行,dispatch_sync就會一直阻塞主線程,造成死鎖。
例2:
```objc
- (void)testAsyncSerialQueue{
// 1.創(chuàng)建一個串行隊(duì)列
dispatch_queue_tqueue = dispatch_queue_create("testAsync.SerialQueue",NULL);
NSLog(@"download之前----%@",[NSThreadcurrentThread]);
// 2.異步執(zhí)行dispatch_async(queue, ^{NSLog(@"sync download之前----%@",[NSThreadcurrentThread]);dispatch_sync(queue, ^{NSLog(@"sync download ----%@",[NSThreadcurrentThread]);
});
NSLog(@"sync download 之后----%@",[NSThreadcurrentThread]); });
dispatch_async(queue, ^{NSLog(@"async download2----%@",[NSThreadcurrentThread]); });
dispatch_async(queue, ^{NSLog(@"async download3----%@",[NSThreadcurrentThread]); });
NSLog(@"async download 之后----%@",[NSThreadcurrentThread]);}```
>打印結(jié)果:
2016-03-09 11:00:27.419 testGCD[5876:2284715] download之前----{number = 1, name = main}
2016-03-09 11:00:27.420 testGCD[5876:2284748] sync download之前----{number = 2, name = (null)}
2016-03-09 11:00:27.420 testGCD[5876:2284715] async download 之后----{number = 1, name = main}
可以看到sync download----和sync download之后----還有async download2----和async download3----這幾個沒有被打印出來。這是為啥?
分析:
首先必須明白,dispatch_queue_create創(chuàng)建一個新隊(duì)列queue, 參數(shù)DISPATCH_QUEUE_SERIAL或NULL表示創(chuàng)建的這是一個串行隊(duì)列。
接著打印download之前----正常;
dispatch_async異步執(zhí)行,當(dāng)前線程不會被阻塞,且具備開啟新線程能力,所以同時有了兩條線程。一條當(dāng)前線程打印出了async download 之后----,正常。另外一條線程執(zhí)行block里的任務(wù),打印sync download image之前----,也是正常。因?yàn)檫@兩條線程是并行,所以并不會相互影響,順序前后也不一定。
之后dispatch_sync同步執(zhí)行,同步任務(wù)會阻塞當(dāng)前所在的線程,直到sync里的任務(wù)執(zhí)行完畢后才會繼續(xù)往下。然后與例1相似,sync把block中的任務(wù)放到隊(duì)列queue當(dāng)中,可是queue是一個串行隊(duì)列,串行隊(duì)列是任務(wù)一個接著一個執(zhí)行,一次只能執(zhí)行一個任務(wù),所以sycn的block里的任務(wù)就必須等到前一個任務(wù)執(zhí)行完畢,可是,前一個正在執(zhí)行的任務(wù)正是被sync阻塞的那個。于是這里又出現(xiàn)了死鎖現(xiàn)象。sycn所在的線程就被卡死了。sync download----和sync download之后----這兩句代碼也就不會被打印出來了。明白不?后面的兩個dispatch_async任務(wù)同樣道理。
例3:
```objc
-(void)testAsyncGlobalQueue{
// 并發(fā)隊(duì)列
dispatch_queue_tqueue =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
//異步 執(zhí)行
dispatch_async(queue, ^{
NSLog(@"-----download1---%@", [NSThreadcurrentThread]); });
dispatch_async(queue, ^{
NSLog(@"-----download2---%@", [NSThreadcurrentThread]); });
dispatch_async(queue, ^{
NSLog(@"-----download3---%@", [NSThreadcurrentThread]); });
dispatch_async(queue, ^{
NSLog(@"-----download4---%@", [NSThreadcurrentThread]); });
dispatch_async(queue, ^{
NSLog(@"-----download5---%@", [NSThreadcurrentThread]); });}```
>打印結(jié)果:
2016-03-09 11:49:44.439 testGCD[6039:2341953] -----download5---{number = 6, name = (null)}
2016-03-09 11:49:44.439 testGCD[6039:2341800] -----download4---{number = 5, name = (null)}
2016-03-09 11:49:44.439 testGCD[6039:2341755] -----download2---{number = 2, name = (null)}
2016-03-09 11:49:44.439 testGCD[6039:2341756] -----download1---{number = 3, name = (null)}
2016-03-09 11:49:44.439 testGCD[6039:2341799] -----download3---{number = 4, name = (null)}
從這個結(jié)果可以看出,異步執(zhí)行任務(wù),一般同時開啟多條線程,且彼此之間不會相互影響,并發(fā)執(zhí)行,哪條線程先后執(zhí)行都不一定。
例4:
```objc
-(void)testSyncGlobalQueue{
// 全局并發(fā)隊(duì)列
dispatch_queue_tqueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
// 同步執(zhí)行dispatch_sync(queue, ^{
NSLog(@"-----download1---%@", [NSThreadcurrentThread]); });
dispatch_sync(queue, ^{
NSLog(@"-----download2---%@", [NSThreadcurrentThread]); });
dispatch_sync(queue, ^{
NSLog(@"-----download3---%@", [NSThreadcurrentThread]); });
dispatch_sync(queue, ^{
NSLog(@"-----download4---%@", [NSThreadcurrentThread]); });
dispatch_sync(queue, ^{
NSLog(@"-----download5---%@", [NSThreadcurrentThread]); });}```
>打印結(jié)果:
2016-03-09 12:07:51.493 testGCD[6150:2359827] -----download1---{number = 1, name = main}
2016-03-09 12:07:51.494 testGCD[6150:2359827] -----download2---{number = 1, name = main}
2016-03-09 12:07:51.494 testGCD[6150:2359827] -----download3---{number = 1, name = main}
2016-03-09 12:07:51.494 testGCD[6150:2359827] -----download4---{number = 1, name = main}
2016-03-09 12:07:51.494 testGCD[6150:2359827] -----download5---{number = 1, name = main}
可以看出,同步任務(wù)不會創(chuàng)建新的線程,串行執(zhí)行一次只能執(zhí)行一個任務(wù),一個接著一個,按順序執(zhí)行。并且這個全局并發(fā)隊(duì)列失去了并發(fā)功能。
例5:
```objc
-(void)testSyncSerialQueue{
//串行隊(duì)列
dispatch_queue_tqueue = dispatch_queue_create("testSync.SerialQueue",NULL);
//同步執(zhí)行
dispatch_sync(queue, ^{
NSLog(@"-----download1---%@", [NSThreadcurrentThread]); });
dispatch_sync(queue, ^{
NSLog(@"-----download2---%@", [NSThreadcurrentThread]); });
dispatch_sync(queue, ^{
NSLog(@"-----download3---%@", [NSThreadcurrentThread]); });
dispatch_sync(queue, ^{
NSLog(@"-----download4---%@", [NSThreadcurrentThread]); });
dispatch_sync(queue, ^{
NSLog(@"-----download5---%@", [NSThreadcurrentThread]); }); }```
>打印結(jié)果:
2016-03-09 12:20:13.188 testGCD[6210:2373033] -----download1---{number = 1, name = main}
2016-03-09 12:20:13.189 testGCD[6210:2373033] -----download2---{number = 1, name = main}
2016-03-09 12:20:13.189 testGCD[6210:2373033] -----download3---{number = 1, name = main}
2016-03-09 12:20:13.189 testGCD[6210:2373033] -----download4---{number = 1, name = main}
2016-03-09 12:20:13.189 testGCD[6210:2373033] -----download5---{number = 1, name = main}
可以看出,同步任務(wù)不會創(chuàng)建新的線程,串行隊(duì)列執(zhí)行一次只能執(zhí)行一個任務(wù),一個接著一個,按順序執(zhí)行。
隊(duì)列組
dispatch_group_async可以實(shí)現(xiàn)監(jiān)聽一組任務(wù)是否完成,完成后得到通知執(zhí)行其他的操作。這個方法很有用,比如你執(zhí)行兩個下載任務(wù),當(dāng)兩個任務(wù)都下載完成后你才通知界面說完成的了。
```objc
-(void)testGCDQueueGroup{ {
// 1.隊(duì)列組
dispatch_group_t group = dispatch_group_create();
// 創(chuàng)建隊(duì)列
dispatch_queue_tqueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
// 2.使用隊(duì)列組的異步方法,下載庫里的圖片
__blockUIImage*image1 =nil;
dispatch_group_async(group, queue, ^{NSURL*url1 = [NSURLURLWithString:@"http://img5.imgtn.bdimg.com/it/u=824940461,4099510846&fm=21&gp=0.jpg"];
NSData*data1 = [NSDatadataWithContentsOfURL:url1];
image1 = [UIImageimageWithData:data1]; });
// 3.使用隊(duì)列組的異步方法,下載圖片
__blockUIImage*image2 =nil;
dispatch_group_async(group, queue, ^{NSURL*url2 = [NSURLURLWithString:@"http://su.bdimg.com/static/superplus/img/logo_white_ee663702.png"];
NSData*data2 = [NSDatadataWithContentsOfURL:url2];
image2 = [UIImageimageWithData:data2]; });
// 4.合并圖片dispatch_group_notify(group, queue, ^{
// 開啟一個位圖上下文UIGraphicsBeginImageContextWithOptions(image1.size,NO,0.0);
// 繪制第1張圖片CGFloatimage1W = image1.size.width;
CGFloatimage1H = image1.size.height;
[image1 drawInRect:CGRectMake(0,0, image1W, image1H)];
// 繪制第2張圖片CGFloatimage2W = image2.size.width*0.5;
CGFloatimage2H = image2.size.height*0.5;CGFloatimage2Y = image1H - image2H;
[image2 drawInRect:CGRectMake(0, image2Y, image2W, image2H)];
// 得到上下文中的圖片
UIImage*fullImage =UIGraphicsGetImageFromCurrentImageContext();
// 結(jié)束上下文
UIGraphicsEndImageContext();
// 5.回到主線程顯示圖片
dispatch_async(dispatch_get_main_queue(), ^{self.imageView.image= fullImage;
});
});
}}```
這個例子就是一個簡單的使用方法,在隊(duì)列里分別下載兩張圖片,等兩張圖片下載完成后,合并成一張圖片。保證執(zhí)行完組里面的所有任務(wù)之后,再執(zhí)行notify函數(shù)里面的block。最后再回到主線程,更新界面并顯示出來。
還有幾點(diǎn)從其他文章看到,貼一下:
dispatch_barrier_async(queue, ^{ });這個方法重點(diǎn)是你傳入的 queue,當(dāng)你傳入的 queue 是通過 DISPATCH_QUEUE_CONCURRENT 參數(shù)自己創(chuàng)建的 queue 時,這個方法會阻塞這個 queue(注意是阻塞 queue ,而不是阻塞當(dāng)前線程),一直等到這個 queue 中排在它前面的任務(wù)都執(zhí)行完成后才會開始執(zhí)行自己,自己執(zhí)行完畢后,再會取消阻塞,使這個 queue 中排在它后面的任務(wù)繼續(xù)執(zhí)行。如果你傳入的是其他的 queue, 那么它就和 dispatch_async 一樣了。 例子代碼如下:
```objc
-(void)testGCDBarrierAsync{
NSLog(@"begin ---%@",[NSThreadcurrentThread]);
dispatch_queue_tqueue = dispatch_queue_create("testGCD.BarrierAsync", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
[NSThreadsleepForTimeInterval:2];
NSLog(@"dispatch_async1");});
dispatch_async(queue, ^{
[NSThreadsleepForTimeInterval:5];
NSLog(@"dispatch_async2");});
dispatch_barrier_async(queue, ^{
NSLog(@"dispatch_barrier_async");
[NSThreadsleepForTimeInterval:5];});
dispatch_async(queue, ^{
[NSThreadsleepForTimeInterval:1];
NSLog(@"dispatch_async3"); });}```
打印結(jié)果:
>2016-03-09 14:42:14.239 testGCD[7320:2534975] begin ---{number = 1, name = main}
2016-03-09 14:42:16.244 testGCD[7320:2535015] dispatch_async1
2016-03-09 14:42:19.241 testGCD[7320:2535016] dispatch_async2
2016-03-09 14:42:19.241 testGCD[7320:2535016] dispatch_barrier_async
2016-03-09 14:42:25.247 testGCD[7320:2535016] dispatch_async3
請注意執(zhí)行的時間,可以看到dispatch_barrier_async是在前面的任務(wù)執(zhí)行結(jié)束后它才執(zhí)行,而且它后面的任務(wù)等它執(zhí)行完成之后才會執(zhí)行。
dispatch_barrier_sync(queue, ^{ });這個方法的使用和上一個一樣,傳入 自定義的并發(fā)隊(duì)列(DISPATCH_QUEUE_CONCURRENT),它和上一個方法一樣的阻塞 queue,不同的是 這個方法還會 阻塞當(dāng)前線程。如果你傳入的是其他的 queue, 那么它就和 dispatch_sync 一樣了。例子如下:
```objc
-(void)testGCDBarrierSync{
NSLog(@"begin ---%@",[NSThreadcurrentThread]);
dispatch_queue_tqueue = dispatch_queue_create("testGCD.BarrierSync", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
[NSThreadsleepForTimeInterval:2];
NSLog(@"dispatch_Sync1");});
dispatch_sync(queue, ^{
[NSThreadsleepForTimeInterval:5];
NSLog(@"dispatch_Sync2");});
dispatch_barrier_sync(queue, ^{
NSLog(@"dispatch_barrier_Sync");
[NSThreadsleepForTimeInterval:5];});
dispatch_sync(queue, ^{
[NSThreadsleepForTimeInterval:3];
NSLog(@"dispatch_Sync3");});}
>打印結(jié)果:
2016-03-09 14:58:10.146 testGCD[7449:2557946] begin ---{number = 1, name = main}
2016-03-09 14:58:12.151 testGCD[7449:2557946] dispatch_Sync1
2016-03-09 14:58:17.153 testGCD[7449:2557946] dispatch_Sync2
2016-03-09 14:58:17.154 testGCD[7449:2557946] dispatch_barrier_Sync
2016-03-09 14:58:25.157 testGCD[7449:2557946] dispatch_Sync3
從這個打印結(jié)果看不出什么,囧。。。但是此時如果UI上有什么交互的話,就不行了。自己可以另行實(shí)驗(yàn)。
`dispatch_apply(times, queue, ^(size_t index) { });`
表現(xiàn)得就像一個 for 循環(huán),但它能并發(fā)地執(zhí)行不同的迭代。這個函數(shù)是同步的,所以和普通的 for 循環(huán)一樣,它只會在所有工作都完成后才會返回。 簡單例子如下:
```objc
-(void)testGCDForApply{
dispatch_queue_tqueue=
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
dispatch_apply(5,queue, ^(size_tindex) {
// 執(zhí)行5次NSLog(@"testGCDForApply");
});
}```
>打印出來的結(jié)果如下:
2016-03-09 15:15:01.340 testGCD[7593:2586389] testGCDForApply
2016-03-09 15:15:01.340 testGCD[7593:2586391] testGCDForApply
2016-03-09 15:15:01.340 testGCD[7593:2584865] testGCDForApply
2016-03-09 15:15:01.340 testGCD[7593:2586252] testGCDForApply
2016-03-09 15:15:01.340 testGCD[7593:2586390] testGCDForApply
其他用法
**延遲執(zhí)行:**所謂延遲執(zhí)行就是延時一段時間再執(zhí)行某段代碼。下面是一些常用方法:
```objc
1.[NSThread sleepForTimeInterval:3];
雖然這種方法能起到延遲執(zhí)行的作用,但別用,因?yàn)闀ㄗ‘?dāng)前線程。
2.[self performSelector:@selector(download:) withObject:@"http://abc.jpg" afterDelay:5];
5秒后自動調(diào)用download:方法,并且傳遞參數(shù)。
3.NSTimer: 也能延遲執(zhí)行。`
[NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:@selector(download:) userInfo:@"http://abc.jpg" repeats:NO];`
4.GCD中的dispatch_after
dispatch_queue_tqueue= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);doubledelay =3;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)),queue, ^{
NSLog(@"------download------%@", [NSThread currentThread]);
});```
**線程安全**:為了防止多個線程搶奪同一個資源造成的數(shù)據(jù)安全問題。線程安全的代碼能在多線程或并發(fā)任務(wù)中被安全的調(diào)用,而不會導(dǎo)致任何問題(數(shù)據(jù)損壞,崩潰,等)。線程不安全的代碼在某個時刻只能在一個上下文中運(yùn)行。也有兩種實(shí)現(xiàn)方法:
互斥鎖:給需要同步的代碼塊加一個互斥鎖,就可以保證每次只有一個線程訪問此代碼塊。
//()小括號里面放的是鎖對象@synchronized(self) {//code here}
2.同步執(zhí)行:把多個線程需要執(zhí)行的代碼添加到同一個串行隊(duì)列中,由于串行隊(duì)列一次只能執(zhí)行一個任務(wù),所以也能實(shí)現(xiàn)線程同步的作用。
```objc
- (void)saleTicket2{
while(1) {
dispatch_sync(ticketQueue, ^{
NSIntegercount =self.leftTicketCount;if(count >0)
{
[NSThreadsleepForTimeInterval:0.05];
self.leftTicketCount=self.leftTicketCount= count -1;
NSLog(@"線程名:%@,售出1,剩余票數(shù)是:%ld,",
[[NSThreadcurrentThread] name],(long)self.leftTicketCount);
}
else
{return;
}
});
}}```
GCD還有一些其他用法,比如創(chuàng)建單例:
**單例模式:**關(guān)于單例,有三件事是你必須要記住的:
單例必須是唯一的,所以它才被稱為單例。在一個應(yīng)用程序的生命周期里,有且只有一個實(shí)例存在。單例的存在給我們提供了一個唯一的全局狀態(tài)。比如我們熟悉的NSNotification,UIApplication和NSUserDefaults都是單例。
為了保持一個單例的唯一性,單例的構(gòu)造器必須是私有的。這防止其他對象也能創(chuàng)建出單例類的實(shí)例。感謝所有幫我指出這點(diǎn)的人。
為了確保單例在應(yīng)用程序的整個生命周期是唯一的,它就必須是線程安全的。當(dāng)你一想到并發(fā)肯定一陣惡心,簡單來說,如果你寫單例的方式是錯誤的,就有可能會有兩個線程嘗試在同一時間初始化同一個單例,這樣你就有潛在的風(fēng)險得到兩個不同的單例。這就意味著我們需要用GCD的dispatch_once來確保初始化單例的代碼在運(yùn)行時只執(zhí)行一次。
```objc
@interfaceLogin:NSObject
+(instancetype)sharedLogin;
@end
@implementationLoginstaticid_instance;
+(instancetype)sharedLogin {
staticdispatch_once_tonceToken;
dispatch_once(&onceToken, ^{
_instance = [[Login alloc] init];
});
return_instance;
}
@end```
關(guān)于GCD目前就想到這些,寫到這。
NSOperation 和 NSOperationQueue
NSOperation
iOS并發(fā)編程中,把每個并發(fā)任務(wù)定義為一個Operation,對應(yīng)的類名是NSOperation。對應(yīng)GCD中的任務(wù)。NSOperation是一個抽象類,無法直接使用,它只定義了Operation的一些基本方法。我們需要創(chuàng)建一個繼承于它的子類或者使用系統(tǒng)預(yù)定義的子類。目前系統(tǒng)預(yù)定義了兩個子類:NSInvocationOperation和NSBlockOperation。創(chuàng)建一個Operation后需要調(diào)用start來啟動任務(wù),它會默認(rèn)在當(dāng)前隊(duì)列中同步執(zhí)行。
NSInvocationOperation
NSInvocationOperation 是一個基于對象和selector的operation,使用這個只需要指定對象以及任務(wù)的selector,還可以設(shè)定傳遞的參數(shù)對象。
```objc
// 創(chuàng)建操作
NSInvocationOperation*operation = [[NSInvocationOperationalloc] initWithTarget:selfselector:@selector(download) object:nil];
// 開始執(zhí)行
operation直接調(diào)用start
是同步執(zhí)行(在當(dāng)前線程執(zhí)行操作)
[operation start];
同時當(dāng)這個operation執(zhí)行完成后,還可以獲取operation中Invocation執(zhí)行后返回的結(jié)果對象:
idresult = [operation result];
NSBlockOperation```
**運(yùn)行一個Operation**
在一個Block中執(zhí)行一個任務(wù),這時我們就需要用到NSBlockOperation??梢酝ㄟ^blockOperationWithBlock:方法來方便地創(chuàng)建一個NSBlockOperation:
//創(chuàng)建NSBlockOperation對象
NSBlockOperation*operation = [NSBlockOperationblockOperationWithBlock:^{
NSLog(@"---download---%@", [NSThreadcurrentThread]); }];
//開始執(zhí)行任務(wù)[operation start];
start方法用來啟動一個Operation任務(wù),這個operation默認(rèn)會在當(dāng)前線程中執(zhí)行。
NSBlockOperation還有一個方法:
addExecutionBlock,通過這個方法可以給operation添加多個執(zhí)行block。這樣 Operation 中的任務(wù)會**并發(fā)執(zhí)行**,它會**在主線程和其它的多個線程**執(zhí)行這些任務(wù):
//創(chuàng)建NSBlockOperation對象
NSBlockOperation*operation = [NSBlockOperationblockOperationWithBlock:^{NSLog(@"operation---%@", [NSThreadcurrentThread]); }];
//添加多個Block
for(NSIntegeri =0; i <6; i++) { [operation addExecutionBlock:^{NSLog(@"---download%ld:%@", i, [NSThreadcurrentThread]); }]; }
//開始執(zhí)行任務(wù)
[operation start];
>注意這打印結(jié)果:
2016-03-10 12:13:17.269 TestOperation[9900:3589128] ---download1:{number = 5, name = (null)}
2016-03-10 12:13:17.269 TestOperation[9900:3589136] ---download4:{number = 7, name = (null)}
2016-03-10 12:13:17.269 TestOperation[9900:3589091] operation---{number = 1, name = main}
2016-03-10 12:13:17.269 TestOperation[9900:3589129] ---download0:{number = 2, name = (null)}
2016-03-10 12:13:17.269 TestOperation[9900:3589137] ---download3:{number = 4, name = (null)}
2016-03-10 12:13:17.269 TestOperation[9900:3589135] ---download5:{number = 6, name = (null)}
2016-03-10 12:13:17.269 TestOperation[9900:3589132] ---download2:{number = 3, name = (null)}
**取消Operation**
要取消一個Operation,要向Operation對象發(fā)送cancel消息:
[operation cancel];
當(dāng)向一個Operation對象發(fā)送cancel消息后,并不保證這個Operation對象一定能立刻取消,這取決于你的main中對cancel的處理。如果你在main方法中沒有對cancel進(jìn)行任何處理的話,發(fā)送cancel消息是沒有任何效果的。為了讓Operation響應(yīng)cancel消息,那么你就要在main方法中一些適當(dāng)?shù)牡胤绞謩拥呐袛鄆sCancelled屬性,如果返回YES的話,應(yīng)釋放相關(guān)資源并立刻停止繼續(xù)執(zhí)行。
自定義Operation
自定義Operation 需要繼承NSOperation,并實(shí)現(xiàn)Operation提供的main方法,你的所有任務(wù)都應(yīng)該在main中進(jìn)行處理。默認(rèn)的start方法中會先做出一些異常判斷然后直接調(diào)用main方法。如果需要自定義一個NSOperation必須重載main方法來執(zhí)行你所想要執(zhí)行的任務(wù)。
@implementationCustomOperation
-(void)main {@try{// Do some work.}@catch(...) {// Exception handle.} }
@end
NSOperationQueue
NSOperationQueue是一個Operation執(zhí)行隊(duì)列,你可以將任何你想要執(zhí)行的Operation添加到Operation Queue中,以在隊(duì)列中執(zhí)行。Operation Queue一共有兩種類型:主隊(duì)列和其他隊(duì)列,只要添加到隊(duì)列中,會自動調(diào)用start()方法執(zhí)行任務(wù),無需再手動添加。
主隊(duì)列
創(chuàng)建主隊(duì)列:
// 主隊(duì)列NSOperationQueue*queue = [NSOperationQueuemainQueue];
其他隊(duì)列
其他隊(duì)列的任務(wù)會在其他線程中并行執(zhí)行。
//創(chuàng)建NSBlockOperation對象NSBlockOperation*operation = [NSBlockOperationblockOperationWithBlock:^{NSLog(@"operation---%@", [NSThreadcurrentThread]);}];
//添加多個Blockfor(NSIntegeri =0; i <6; i++) { [operation addExecutionBlock:^{NSLog(@"---download%ld:%@", i, [NSThreadcurrentThread]); }];}
//開始任務(wù),用NSOperationQueue無需手動start方法
// [operation start];// 創(chuàng)建隊(duì)列NSOperationQueue*queue = [[NSOperationQueuealloc] init];
// 添加任務(wù)到隊(duì)列中(自動異步執(zhí)行)[queue addOperation:operation];
>打印結(jié)果:
2016-03-10 14:06:03.208 TestOperation[10144:3718109] ---download1:{number = 8, name = (null)}
2016-03-10 14:06:03.208 TestOperation[10144:3718203] ---download0:{number = 7, name = (null)}
2016-03-10 14:06:03.208 TestOperation[10144:3718107] operation---{number = 2, name = (null)}
2016-03-10 14:06:03.208 TestOperation[10144:3718240] ---download4:{number = 5, name = (null)}
2016-03-10 14:06:03.208 TestOperation[10144:3718239] ---download3:{number = 4, name = (null)}
2016-03-10 14:06:03.208 TestOperation[10144:3718241] ---download5:{number = 6, name = (null)}
2016-03-10 14:06:03.208 TestOperation[10144:3718204] ---download2:{number = 3, name = (null)}
NSOperationQueue還有一個添加任務(wù)的方法addOperationWithBlock
NSOperationQueue*queue = [[NSOperationQueuealloc] init];[queue addOperationWithBlock:^{
// 異步下載圖片NSURL*url = [NSURLURLWithString:@"http://d.hiphotos.baidu.com/image/pic/item/37d3d539b6003af3290eaf5d362ac65c1038b652.jpg"];NSData*data = [NSDatadataWithContentsOfURL:url];UIImage*image = [UIImageimageWithData:data];// 回到主線程,顯示圖片[[NSOperationQueuemainQueue] addOperationWithBlock:^{self.imageView.image= image; }];}];
**最大并發(fā)Operation數(shù)目**
在一個Operation Queue中是可以同時執(zhí)行多個Operation的,Operation Queue會動態(tài)的創(chuàng)建多個線程來完成相應(yīng)Operation。具體的線程數(shù)是由Operation Queue來優(yōu)化配置的,這一般取決與系統(tǒng)CPU的性能,比如CPU的核心數(shù),和CPU的負(fù)載。但我們還是可以設(shè)置一個最大并發(fā)數(shù)的,那么Operation Queue就不會創(chuàng)建超過最大并發(fā)數(shù)量的線程。當(dāng)設(shè)置最大并發(fā)數(shù)位1時,隊(duì)列中每次只能執(zhí)行一個任務(wù),這就是一個串行執(zhí)行隊(duì)列了。
NSOperationQueue *queue= [[NSOperationQueue alloc] init];queue.maxConcurrentOperationCount =1;
**Operation的依賴關(guān)系**
有時候我們對任務(wù)的執(zhí)行順序有要求,一個任務(wù)必須在另一個任務(wù)執(zhí)行之前完成,這就需要用到Operation的依賴(Dependency)屬性。我們可以為每個Operation設(shè)定一些依賴的另外一些Operation,那么如果依賴的Operation沒有全部執(zhí)行完畢,這個Operation就不會被執(zhí)行。比如有個操作C依賴操作B,操作b又依賴操作A:
// 1.創(chuàng)建一個隊(duì)列(非主隊(duì)列)NSOperationQueue*queue = [[NSOperationQueuealloc] init];// 2.創(chuàng)建3個操作NSBlockOperation*operationC = [NSBlockOperationblockOperationWithBlock:^{NSLog(@"---操作C---%@", [NSThreadcurrentThread]);}];NSBlockOperation*operationA = [NSBlockOperationblockOperationWithBlock:^{NSLog(@"---操作A---%@", [NSThreadcurrentThread]);}];NSBlockOperation*operationB = [NSBlockOperationblockOperationWithBlock:^{NSLog(@"---操作B---%@", [NSThreadcurrentThread]);}];// 設(shè)置依賴[operationB addDependency:operationA];[operationC addDependency:operationB];// 3.添加操作到隊(duì)列中(自動異步執(zhí)行任務(wù))[queue addOperation:operationA];[queue addOperation:operationB];[queue addOperation:operationC];
打印輸出:
2016-03-10 14:52:01.518 TestOperation[10812:3782938] ---操作A---{number = 2, name = (null)}
2016-03-10 14:52:01.519 TestOperation[10812:3782945] ---操作B---{number = 3, name = (null)}
2016-03-10 14:52:01.519 TestOperation[10812:3782945] ---操作C---{number = 3, name = (null)}
如果將這些operation和它所依賴的operation加入某個隊(duì)列中,那么這些operation只有在它所依賴的operation都執(zhí)行完畢后才可以被執(zhí)行。注意:
不能相互添加依賴,比如operationA依賴operationB,operationB 又依賴operationA,這會造成死鎖。
可以在不同隊(duì)列之間添加依賴。
可以使用removeDependency解除依賴。
**Operation在隊(duì)列中執(zhí)行的優(yōu)先級**
Operation在隊(duì)列中默認(rèn)是按FIFO(First In First Out)順序執(zhí)行的。同時我們可以為單個的Operation設(shè)置一個執(zhí)行的優(yōu)先級queuePriority,打亂這個順序。當(dāng)Queue有空閑資源執(zhí)行新的Operation時,會優(yōu)先執(zhí)行當(dāng)前隊(duì)列中優(yōu)先級最高的待執(zhí)行Operation。
我從網(wǎng)上copy了一小段代碼來展示一下隊(duì)列優(yōu)先級效果:
NSBlockOperation*operation1 = [NSBlockOperationblockOperationWithBlock:^{NSLog(@"------operation1 begin------"); sleep(5);NSLog(@"------operation1 end------");}];operation1.queuePriority=NSOperationQueuePriorityHigh;NSBlockOperation*operation2 = [NSBlockOperationblockOperationWithBlock:^{NSLog(@"------operation2 begin------"); sleep(1);NSLog(@"------operation2 end------");}];NSBlockOperation*operation3 = [NSBlockOperationblockOperationWithBlock:^{NSLog(@"------operation3 begin------"); sleep(2);NSLog(@"------operation3 end------");}];operation2.completionBlock= ^{NSLog(@"------operation2 finished in completionBlock------");};NSOperationQueue*queue = [[NSOperationQueuealloc] init];queue.maxConcurrentOperationCount=1;[queue addOperation:operation2];[queue addOperation:operation3];[queue addOperation:operation1];[queue waitUntilAllOperationsAreFinished];
設(shè)置最大并發(fā)數(shù)為1,以便任務(wù)能一個一個的執(zhí)行。打印輸出:
2016-03-10 15:26:11.790 TestOperation[11068:3829921] -----test-----{number = 1, name = main}
2016-03-10 15:26:11.791 TestOperation[11068:3829995] ------operation2 begin------
2016-03-10 15:26:12.864 TestOperation[11068:3829995] ------operation2 end------
2016-03-10 15:26:12.865 TestOperation[11068:3830132] ------operation2 finished in completionBlock------
2016-03-10 15:26:12.865 TestOperation[11068:3829996] ------operation1 begin------
2016-03-10 15:26:17.934 TestOperation[11068:3829996] ------operation1 end------
2016-03-10 15:26:17.934 TestOperation[11068:3830132] ------operation3 begin------
2016-03-10 15:26:20.006 TestOperation[11068:3830132] ------operation3 end------
從這個輸出結(jié)果可以看出,operation2執(zhí)行完畢后會執(zhí)行operation1,而不是operation3,這是因?yàn)閛peration1的優(yōu)先級是NSOperationQueuePriorityHigh。這里還要注意點(diǎn)一就是,**第一個加入到Operation Queue中的Operation,無論它的優(yōu)先級有多么低,總是會第一個執(zhí)行**。
**設(shè)置Operation的completionBlock**
每個Operation都可以設(shè)置一個completionBlock,在Operation執(zhí)行完成時自動執(zhí)行這個Block。我們可以在此進(jìn)行一些完成的處理。completionBlock實(shí)現(xiàn)原理是對Operation的isFinnshed字段進(jìn)行KVO(Key-Value Observing),當(dāng)監(jiān)聽到isFinnished變成YES時,就執(zhí)行completionBlock。上面的小段代碼可以看出operation2的block執(zhí)行完畢后立刻執(zhí)行completionBlock。
其他方法
**設(shè)置Operation的線程優(yōu)先級**:我們可以為Operation設(shè)置一個線程優(yōu)先級,即threadPriority。那么執(zhí)行main的時候,線程優(yōu)先級就會調(diào)整到所設(shè)置的線程優(yōu)先級。這個默認(rèn)值是0.5,我們可以在Operation執(zhí)行前修改它。
operation.threadPriority = 0.1;
注意:如果你重載的start方法,那么你需要自己來配置main執(zhí)行時的線程優(yōu)先級和threadPriority字段保持一致。
NSOperation
//判斷任務(wù)是否正在執(zhí)行BOOLexecuting;//判斷任務(wù)是否完成BOOLfinished;//取消任務(wù)-(void)cancel;//阻塞當(dāng)前線程直到此任務(wù)執(zhí)行結(jié)束-(void)waitUntilFinished//是否異步執(zhí)行任務(wù)BOOLasynchronous
NSOperationQueue
//獲取隊(duì)列任務(wù)數(shù)NSUIntegeroperationCount//暫?;蚴侨∠蝿?wù)BOOLsuspended;//取消所有任務(wù)- (void)cancelAllOperations;//阻塞當(dāng)前線程直到隊(duì)列中的所有任務(wù)執(zhí)行結(jié)束- (void)waitUntilAllOperationsAreFinished;
在最后再看看有哪些方式可以**從其他線程回到主線程:**
NSThead
[selfperformSelectorOnMainThread:@selector(run) withObject:nilwaitUntilDone:NO];
GCD
dispatch_async(dispatch_get_main_queue(), ^{});
NSOperationQueue
[[NSOperationQueue mainQueue]addOperationWithBlock:^{
}];
關(guān)于多線程編程,還有其他很多功能,這里所寫的只是自己平時比較常用的。具體可以查看[官方文檔](https://developer.apple.com/library/ios/documentation/Performance/Reference/GCD_libdispatch_Ref/index.html#//apple_ref/doc/uid/TP40008079),或是其他[資料](http://www.cocoachina.com/industry/20140515/8433.html)。本篇GCD的代碼例子可以到[github](https://github.com/acqiang/TestGCD)上查看,NSOperation的代碼在[這里](https://github.com/acqiang/TestOperation)
最后聲明:
本文所記載的東西都是來自網(wǎng)上資料或是官方文檔,如有侵權(quán),請及時告訴我,但所有代碼我都親自測試了一遍,知識水平有限,如果有錯可隨時指證,謝謝?。?!希望站在巨人的肩膀上看得更遠(yuǎn)一些。