iOS開發(fā)中常用的幾種多線程方案饼煞,簡(jiǎn)單做個(gè)小結(jié)喧兄,方便日后查閱狼荞。
- NSThead
- GCD
- NSOperation & NSOpeartionQueue
- Pthreads
這種方式不用介紹(我也不太會(huì)使用),一般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);
}
通過(guò)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)建了一個(gè)線程,但并沒有銷毀爷狈,我們需要手動(dòng)管理線程的生命周期植影。隨意感受一下就好了。涎永。思币。
NSThead
優(yōu)點(diǎn):NSThread 比其他幾個(gè)輕量級(jí)。
缺點(diǎn):需要自己管理線程的生命周期羡微,線程同步谷饿。線程同步對(duì)數(shù)據(jù)的加鎖會(huì)有一定的系統(tǒng)開銷。
創(chuàng)建和啟動(dòng)線程的3種方式:
1.先創(chuàng)建后啟動(dòng):
// 創(chuàng)建線程N(yùn)SThread*thread = [[NSThreadalloc] initWithTarget:selfselector:@selector(download:) object:@"http://b.png"];
thread.name=@"下載線程";
// 啟動(dòng)線程(調(diào)用self的download方法)
[thread start];
selector :線程執(zhí)行的方法妈倔,這個(gè)selector只能有一個(gè)參數(shù)博投,而且不能有返回值。
target :selector消息發(fā)送的對(duì)象
argument:傳輸給target的唯一參數(shù)盯蝴,也可以是nil
2.創(chuàng)建并自動(dòng)啟動(dòng)線程:
[NSThreaddetachNewThreadSelector:@selector(download:) toTarget:selfwithObject:@"http://a.jpg"];
3.使用NSObject方法創(chuàng)建線程并自動(dòng)啟動(dò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 簡(jiǎn)稱(GCD)是蘋果公司開發(fā)的技術(shù)毅哗,以優(yōu)化的應(yīng)用程序支持多核心處理器和其他的對(duì)稱多處理系統(tǒng)的系統(tǒng)。這建立在任務(wù)并行執(zhí)行的線程池模式的基礎(chǔ)上的捧挺。它首次發(fā)布在Mac OS X 10.6 虑绵,iOS 4及以上也可用。GCD的工作原理是:讓程序平行排隊(duì)的特定任務(wù)闽烙,根據(jù)可用的處理資源翅睛,安排他們?cè)谌魏慰捎玫奶幚砥骱诵纳蠄?zhí)行任務(wù)。它是蘋果為多核的并行運(yùn)算提出的解決方案黑竞,所以會(huì)自動(dòng)合理地利用更多的CPU內(nèi)核(比如雙核捕发、四核),最重要的是它會(huì)自動(dòng)管理線程的生命周期(創(chuàng)建線程摊溶、調(diào)度任務(wù)爬骤、銷毀線程),完全不需要我們管理莫换,我們只需要告訴干什么就行霞玄。
任務(wù)和隊(duì)列
任務(wù):
需要執(zhí)行什么操作,一個(gè)任務(wù)可以是一個(gè)函數(shù)(function)或者是一個(gè)block拉岁。任務(wù)有兩種執(zhí)行方式:同步執(zhí)行和異步執(zhí)行坷剧,它們區(qū)別在于是否會(huì)阻塞當(dāng)前線程,直到任務(wù)執(zhí)行完喊暖。如果是 同步(sync) 操作惫企,它會(huì)阻塞當(dāng)前線程并等待 Block 中的任務(wù)執(zhí)行完畢,然后當(dāng)前線程才會(huì)繼續(xù)往下運(yùn)行。如果是 異步(async)操作狞尔,當(dāng)前線程會(huì)直接往下執(zhí)行丛版,它不會(huì)阻塞當(dāng)前線程。
創(chuàng)建任務(wù)方式:
同步任務(wù):會(huì)阻塞當(dāng)前線程偏序,并且不具備開啟新線程的能力页畦。
dispatch_sync(<#queue#>, ^{//code here});
異步任務(wù):不會(huì)阻塞當(dāng)前線程,并且具有開啟新線程的能力研儒。
dispatch_async(<#queue#>, ^{//code here});
隊(duì)列:
存放任務(wù)豫缨。有串行隊(duì)列和并行隊(duì)列兩種。
串行隊(duì)列:GCD中的FIFO隊(duì)列稱為dispatch queue端朵,它可以保證先進(jìn)來(lái)的任務(wù)先得到執(zhí)行,然后再取下一個(gè)好芭,一個(gè)接著一個(gè)執(zhí)行。
并行隊(duì)列:放到并行隊(duì)列的任務(wù)可以并發(fā)執(zhí)行冲呢。不過(guò)需要注意舍败,GCD 會(huì)根據(jù)系統(tǒng)資源控制并行的數(shù)量,所以如果任務(wù)很多碗硬,它并不會(huì)讓所有任務(wù)同時(shí)執(zhí)行瓤湘。
創(chuàng)建隊(duì)列方式
主隊(duì)列:這是一個(gè)特殊的串行隊(duì)列,主要用于刷新UI界面恩尾,處理UI控件的事件弛说。如下:
dispatch_queue_t queue = dispatch_get_main_queue();
自建創(chuàng)建的隊(duì)列:自己可以創(chuàng)建串行隊(duì)列,也可以創(chuàng)建 并行隊(duì)列。它有兩個(gè)參數(shù):
第一個(gè)參數(shù):標(biāo)識(shí)符翰意,用于debug的時(shí)候標(biāo)識(shí)唯一的隊(duì)列木人。
第二個(gè)參數(shù):用來(lái)表示創(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ù)一般都加入到這個(gè)隊(duì)列中进鸠。
`dispatch_queue_tqueue= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);`
下面的例子能更好的理解同步和異步和各種隊(duì)列的使用:
例1:
```objc
//sync -- 主隊(duì)列(不能用---會(huì)卡死)
-(void)testSyncMainQueue{
NSLog(@"download之前----%@",[NSThreadcurrentThread]);
// 1.主隊(duì)列(添加到主隊(duì)列中的任務(wù)稠曼,都會(huì)自動(dòng)放到主線程中去執(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ù)會(huì)阻塞當(dāng)前的主線程客年,然后把block中的任務(wù)放到主隊(duì)列queue當(dāng)中霞幅,可是添加到主隊(duì)列中的任務(wù),都會(huì)自動(dòng)放到主線程中去執(zhí)行量瓜,但是主線程已被阻塞司恳,所以block中download image無(wú)法執(zhí)行,dispatch_sync就會(huì)一直阻塞主線程绍傲,造成死鎖扔傅。
例2:
```objc
- (void)testAsyncSerialQueue{
// 1.創(chuàng)建一個(gè)串行隊(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----這幾個(gè)沒有被打印出來(lái)。這是為啥?
分析:
首先必須明白猎塞,dispatch_queue_create創(chuàng)建一個(gè)新隊(duì)列queue试读, 參數(shù)DISPATCH_QUEUE_SERIAL或NULL表示創(chuàng)建的這是一個(gè)串行隊(duì)列。
接著打印download之前----正常;
dispatch_async異步執(zhí)行邢享,當(dāng)前線程不會(huì)被阻塞鹏往,且具備開啟新線程能力,所以同時(shí)有了兩條線程骇塘。一條當(dāng)前線程打印出了async download 之后----,正常韩容。另外一條線程執(zhí)行block里的任務(wù)款违,打印sync download image之前----,也是正常群凶。因?yàn)檫@兩條線程是并行插爹,所以并不會(huì)相互影響,順序前后也不一定请梢。
之后dispatch_sync同步執(zhí)行赠尾,同步任務(wù)會(huì)阻塞當(dāng)前所在的線程,直到sync里的任務(wù)執(zhí)行完畢后才會(huì)繼續(xù)往下毅弧。然后與例1相似气嫁,sync把block中的任務(wù)放到隊(duì)列queue當(dāng)中,可是queue是一個(gè)串行隊(duì)列够坐,串行隊(duì)列是任務(wù)一個(gè)接著一個(gè)執(zhí)行寸宵,一次只能執(zhí)行一個(gè)任務(wù),所以sycn的block里的任務(wù)就必須等到前一個(gè)任務(wù)執(zhí)行完畢元咙,可是梯影,前一個(gè)正在執(zhí)行的任務(wù)正是被sync阻塞的那個(gè)。于是這里又出現(xiàn)了死鎖現(xiàn)象庶香。sycn所在的線程就被卡死了甲棍。sync download----和sync download之后----這兩句代碼也就不會(huì)被打印出來(lái)了。明白不赶掖?后面的兩個(gè)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)}
從這個(gè)結(jié)果可以看出,異步執(zhí)行任務(wù)倘零,一般同時(shí)開啟多條線程唱遭,且彼此之間不會(huì)相互影響,并發(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ù)不會(huì)創(chuàng)建新的線程,串行執(zhí)行一次只能執(zhí)行一個(gè)任務(wù)司致,一個(gè)接著一個(gè)拆吆,按順序執(zhí)行。并且這個(gè)全局并發(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ù)不會(huì)創(chuàng)建新的線程,串行隊(duì)列執(zhí)行一次只能執(zhí)行一個(gè)任務(wù)庭再,一個(gè)接著一個(gè)捞奕,按順序執(zhí)行。
隊(duì)列組
dispatch_group_async可以實(shí)現(xiàn)監(jiān)聽一組任務(wù)是否完成拄轻,完成后得到通知執(zhí)行其他的操作颅围。這個(gè)方法很有用,比如你執(zhí)行兩個(gè)下載任務(wù)恨搓,當(dāng)兩個(gè)任務(wù)都下載完成后你才通知界面說(shuō)完成的了院促。
```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ì)列組的異步方法,下載庫(kù)里的圖片
__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, ^{
// 開啟一個(gè)位圖上下文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;
});
});
}}```
這個(gè)例子就是一個(gè)簡(jiǎn)單的使用方法常拓,在隊(duì)列里分別下載兩張圖片,等兩張圖片下載完成后辉浦,合并成一張圖片弄抬。保證執(zhí)行完組里面的所有任務(wù)之后,再執(zhí)行notify函數(shù)里面的block盏浙。最后再回到主線程眉睹,更新界面并顯示出來(lái)。
還有幾點(diǎn)從其他文章看到废膘,貼一下:
dispatch_barrier_async(queue, ^{ });這個(gè)方法重點(diǎn)是你傳入的 queue竹海,當(dāng)你傳入的 queue 是通過(guò) DISPATCH_QUEUE_CONCURRENT 參數(shù)自己創(chuàng)建的 queue 時(shí),這個(gè)方法會(huì)阻塞這個(gè) queue(注意是阻塞 queue 丐黄,而不是阻塞當(dāng)前線程)斋配,一直等到這個(gè) queue 中排在它前面的任務(wù)都執(zhí)行完成后才會(huì)開始執(zhí)行自己,自己執(zhí)行完畢后灌闺,再會(huì)取消阻塞艰争,使這個(gè) 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
請(qǐng)注意執(zhí)行的時(shí)間甩卓,可以看到dispatch_barrier_async是在前面的任務(wù)執(zhí)行結(jié)束后它才執(zhí)行,而且它后面的任務(wù)等它執(zhí)行完成之后才會(huì)執(zhí)行蕉斜。
dispatch_barrier_sync(queue, ^{ });這個(gè)方法的使用和上一個(gè)一樣逾柿,傳入 自定義的并發(fā)隊(duì)列(DISPATCH_QUEUE_CONCURRENT)缀棍,它和上一個(gè)方法一樣的阻塞 queue,不同的是 這個(gè)方法還會(huì) 阻塞當(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
從這個(gè)打印結(jié)果看不出什么,囧弱匪。青瀑。。但是此時(shí)如果UI上有什么交互的話萧诫,就不行了斥难。自己可以另行實(shí)驗(yàn)。
`dispatch_apply(times, queue, ^(size_t index) { });`
表現(xiàn)得就像一個(gè) for 循環(huán)财搁,但它能并發(fā)地執(zhí)行不同的迭代蘸炸。這個(gè)函數(shù)是同步的,所以和普通的 for 循環(huán)一樣尖奔,它只會(huì)在所有工作都完成后才會(huì)返回。 簡(jiǎ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");
});
}```
>打印出來(lái)的結(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í)行就是延時(shí)一段時(shí)間再執(zhí)行某段代碼穷当。下面是一些常用方法:
```objc
1.[NSThread sleepForTimeInterval:3];
雖然這種方法能起到延遲執(zhí)行的作用提茁,但別用,因?yàn)闀?huì)卡住當(dāng)前線程馁菜。
2.[self performSelector:@selector(download:) withObject:@"http://abc.jpg" afterDelay:5];
5秒后自動(dòng)調(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]);
});```
**線程安全**:為了防止多個(gè)線程搶奪同一個(gè)資源造成的數(shù)據(jù)安全問(wèn)題峭火。線程安全的代碼能在多線程或并發(fā)任務(wù)中被安全的調(diào)用,而不會(huì)導(dǎo)致任何問(wèn)題(數(shù)據(jù)損壞智嚷,崩潰卖丸,等)。線程不安全的代碼在某個(gè)時(shí)刻只能在一個(gè)上下文中運(yùn)行盏道。也有兩種實(shí)現(xiàn)方法:
互斥鎖:給需要同步的代碼塊加一個(gè)互斥鎖稍浆,就可以保證每次只有一個(gè)線程訪問(wèn)此代碼塊。
//()小括號(hào)里面放的是鎖對(duì)象@synchronized(self) {//code here}
2.同步執(zhí)行:把多個(gè)線程需要執(zhí)行的代碼添加到同一個(gè)串行隊(duì)列中猜嘱,由于串行隊(duì)列一次只能執(zhí)行一個(gè)任務(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)于單例弦撩,有三件事是你必須要記住的:
單例必須是唯一的,所以它才被稱為單例论皆。在一個(gè)應(yīng)用程序的生命周期里益楼,有且只有一個(gè)實(shí)例存在猾漫。單例的存在給我們提供了一個(gè)唯一的全局狀態(tài)。比如我們熟悉的NSNotification偏形,UIApplication和NSUserDefaults都是單例静袖。
為了保持一個(gè)單例的唯一性,單例的構(gòu)造器必須是私有的俊扭。這防止其他對(duì)象也能創(chuàng)建出單例類的實(shí)例队橙。感謝所有幫我指出這點(diǎn)的人。
為了確保單例在應(yīng)用程序的整個(gè)生命周期是唯一的萨惑,它就必須是線程安全的捐康。當(dāng)你一想到并發(fā)肯定一陣惡心,簡(jiǎn)單來(lái)說(shuō)庸蔼,如果你寫單例的方式是錯(cuò)誤的解总,就有可能會(huì)有兩個(gè)線程嘗試在同一時(shí)間初始化同一個(gè)單例,這樣你就有潛在的風(fēng)險(xiǎn)得到兩個(gè)不同的單例姐仅。這就意味著我們需要用GCD的dispatch_once來(lái)確保初始化單例的代碼在運(yùn)行時(shí)只執(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ā)編程中劳翰,把每個(gè)并發(fā)任務(wù)定義為一個(gè)Operation,對(duì)應(yīng)的類名是NSOperation馒疹。對(duì)應(yīng)GCD中的任務(wù)佳簸。NSOperation是一個(gè)抽象類,無(wú)法直接使用颖变,它只定義了Operation的一些基本方法生均。我們需要?jiǎng)?chuàng)建一個(gè)繼承于它的子類或者使用系統(tǒng)預(yù)定義的子類。目前系統(tǒng)預(yù)定義了兩個(gè)子類:NSInvocationOperation和NSBlockOperation腥刹。創(chuàng)建一個(gè)Operation后需要調(diào)用start來(lái)啟動(dòng)任務(wù)马胧,它會(huì)默認(rèn)在當(dāng)前隊(duì)列中同步執(zhí)行。
NSInvocationOperation
NSInvocationOperation 是一個(gè)基于對(duì)象和selector的operation肛走,使用這個(gè)只需要指定對(duì)象以及任務(wù)的selector漓雅,還可以設(shè)定傳遞的參數(shù)對(duì)象。
```objc
// 創(chuàng)建操作
NSInvocationOperation*operation = [[NSInvocationOperationalloc] initWithTarget:selfselector:@selector(download) object:nil];
// 開始執(zhí)行
operation直接調(diào)用start
是同步執(zhí)行(在當(dāng)前線程執(zhí)行操作)
[operation start];
同時(shí)當(dāng)這個(gè)operation執(zhí)行完成后,還可以獲取operation中Invocation執(zhí)行后返回的結(jié)果對(duì)象:
idresult = [operation result];
NSBlockOperation```
**運(yùn)行一個(gè)Operation**
在一個(gè)Block中執(zhí)行一個(gè)任務(wù),這時(shí)我們就需要用到NSBlockOperation昆汹∷┲瘢可以通過(guò)blockOperationWithBlock:方法來(lái)方便地創(chuàng)建一個(gè)NSBlockOperation:
//創(chuàng)建NSBlockOperation對(duì)象
NSBlockOperation*operation = [NSBlockOperationblockOperationWithBlock:^{
NSLog(@"---download---%@", [NSThreadcurrentThread]); }];
//開始執(zhí)行任務(wù)[operation start];
start方法用來(lái)啟動(dòng)一個(gè)Operation任務(wù),這個(gè)operation默認(rèn)會(huì)在當(dāng)前線程中執(zhí)行。
NSBlockOperation還有一個(gè)方法:
addExecutionBlock,通過(guò)這個(gè)方法可以給operation添加多個(gè)執(zhí)行block。這樣 Operation 中的任務(wù)會(huì)**并發(fā)執(zhí)行**,它會(huì)**在主線程和其它的多個(gè)線程**執(zhí)行這些任務(wù):
//創(chuàng)建NSBlockOperation對(duì)象
NSBlockOperation*operation = [NSBlockOperationblockOperationWithBlock:^{NSLog(@"operation---%@", [NSThreadcurrentThread]); }];
//添加多個(gè)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**
要取消一個(gè)Operation旺遮,要向Operation對(duì)象發(fā)送cancel消息:
[operation cancel];
當(dāng)向一個(gè)Operation對(duì)象發(fā)送cancel消息后赵讯,并不保證這個(gè)Operation對(duì)象一定能立刻取消,這取決于你的main中對(duì)cancel的處理耿眉。如果你在main方法中沒有對(duì)cancel進(jìn)行任何處理的話边翼,發(fā)送cancel消息是沒有任何效果的。為了讓Operation響應(yīng)cancel消息鸣剪,那么你就要在main方法中一些適當(dāng)?shù)牡胤绞謩?dòng)的判斷isCancelled屬性组底,如果返回YES的話,應(yīng)釋放相關(guān)資源并立刻停止繼續(xù)執(zhí)行筐骇。
自定義Operation
自定義Operation 需要繼承NSOperation,并實(shí)現(xiàn)Operation提供的main方法债鸡,你的所有任務(wù)都應(yīng)該在main中進(jìn)行處理。默認(rèn)的start方法中會(huì)先做出一些異常判斷然后直接調(diào)用main方法铛纬。如果需要自定義一個(gè)NSOperation必須重載main方法來(lái)執(zhí)行你所想要執(zhí)行的任務(wù)厌均。
@implementationCustomOperation
-(void)main {@try{// Do some work.}@catch(...) {// Exception handle.} }
@end
NSOperationQueue
NSOperationQueue是一個(gè)Operation執(zhí)行隊(duì)列,你可以將任何你想要執(zhí)行的Operation添加到Operation Queue中告唆,以在隊(duì)列中執(zhí)行棺弊。Operation Queue一共有兩種類型:主隊(duì)列和其他隊(duì)列,只要添加到隊(duì)列中擒悬,會(huì)自動(dòng)調(diào)用start()方法執(zhí)行任務(wù)镊屎,無(wú)需再手動(dòng)添加。
主隊(duì)列
創(chuàng)建主隊(duì)列:
// 主隊(duì)列NSOperationQueue*queue = [NSOperationQueuemainQueue];
其他隊(duì)列
其他隊(duì)列的任務(wù)會(huì)在其他線程中并行執(zhí)行茄螃。
//創(chuàng)建NSBlockOperation對(duì)象NSBlockOperation*operation = [NSBlockOperationblockOperationWithBlock:^{NSLog(@"operation---%@", [NSThreadcurrentThread]);}];
//添加多個(gè)Blockfor(NSIntegeri =0; i <6; i++) { [operation addExecutionBlock:^{NSLog(@"---download%ld:%@", i, [NSThreadcurrentThread]); }];}
//開始任務(wù),用NSOperationQueue無(wú)需手動(dòng)start方法
// [operation start];// 創(chuàng)建隊(duì)列NSOperationQueue*queue = [[NSOperationQueuealloc] init];
// 添加任務(wù)到隊(duì)列中(自動(dòng)異步執(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還有一個(gè)添加任務(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ù)目**
在一個(gè)Operation Queue中是可以同時(shí)執(zhí)行多個(gè)Operation的归苍,Operation Queue會(huì)動(dòng)態(tài)的創(chuàng)建多個(gè)線程來(lái)完成相應(yīng)Operation。具體的線程數(shù)是由Operation Queue來(lái)優(yōu)化配置的运怖,這一般取決與系統(tǒng)CPU的性能拼弃,比如CPU的核心數(shù),和CPU的負(fù)載摇展。但我們還是可以設(shè)置一個(gè)最大并發(fā)數(shù)的吻氧,那么Operation Queue就不會(huì)創(chuàng)建超過(guò)最大并發(fā)數(shù)量的線程。當(dāng)設(shè)置最大并發(fā)數(shù)位1時(shí)咏连,隊(duì)列中每次只能執(zhí)行一個(gè)任務(wù)盯孙,這就是一個(gè)串行執(zhí)行隊(duì)列了。
NSOperationQueue *queue= [[NSOperationQueue alloc] init];queue.maxConcurrentOperationCount =1;
**Operation的依賴關(guān)系**
有時(shí)候我們對(duì)任務(wù)的執(zhí)行順序有要求祟滴,一個(gè)任務(wù)必須在另一個(gè)任務(wù)執(zhí)行之前完成振惰,這就需要用到Operation的依賴(Dependency)屬性。我們可以為每個(gè)Operation設(shè)定一些依賴的另外一些Operation垄懂,那么如果依賴的Operation沒有全部執(zhí)行完畢骑晶,這個(gè)Operation就不會(huì)被執(zhí)行痛垛。比如有個(gè)操作C依賴操作B,操作b又依賴操作A:
// 1.創(chuàng)建一個(gè)隊(duì)列(非主隊(duì)列)NSOperationQueue*queue = [[NSOperationQueuealloc] init];// 2.創(chuàng)建3個(gè)操作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ì)列中(自動(dòng)異步執(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加入某個(gè)隊(duì)列中桶蛔,那么這些operation只有在它所依賴的operation都執(zhí)行完畢后才可以被執(zhí)行匙头。注意:
不能相互添加依賴,比如operationA依賴operationB仔雷,operationB 又依賴operationA蹂析,這會(huì)造成死鎖。
可以在不同隊(duì)列之間添加依賴朽寞。
可以使用removeDependency解除依賴识窿。
**Operation在隊(duì)列中執(zhí)行的優(yōu)先級(jí)**
Operation在隊(duì)列中默認(rèn)是按FIFO(First In First Out)順序執(zhí)行的。同時(shí)我們可以為單個(gè)的Operation設(shè)置一個(gè)執(zhí)行的優(yōu)先級(jí)queuePriority脑融,打亂這個(gè)順序喻频。當(dāng)Queue有空閑資源執(zhí)行新的Operation時(shí),會(huì)優(yōu)先執(zhí)行當(dāng)前隊(duì)列中優(yōu)先級(jí)最高的待執(zhí)行Operation肘迎。
我從網(wǎng)上copy了一小段代碼來(lái)展示一下隊(duì)列優(yōu)先級(jí)效果:
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ù)能一個(gè)一個(gè)的執(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------
從這個(gè)輸出結(jié)果可以看出妓布,operation2執(zhí)行完畢后會(huì)執(zhí)行operation1姻蚓,而不是operation3,這是因?yàn)閛peration1的優(yōu)先級(jí)是NSOperationQueuePriorityHigh匣沼。這里還要注意點(diǎn)一就是狰挡,**第一個(gè)加入到Operation Queue中的Operation,無(wú)論它的優(yōu)先級(jí)有多么低释涛,總是會(huì)第一個(gè)執(zhí)行**加叁。
**設(shè)置Operation的completionBlock**
每個(gè)Operation都可以設(shè)置一個(gè)completionBlock,在Operation執(zhí)行完成時(shí)自動(dòng)執(zhí)行這個(gè)Block唇撬。我們可以在此進(jìn)行一些完成的處理它匕。completionBlock實(shí)現(xiàn)原理是對(duì)Operation的isFinnshed字段進(jìn)行KVO(Key-Value Observing),當(dāng)監(jiān)聽到isFinnished變成YES時(shí)窖认,就執(zhí)行completionBlock豫柬。上面的小段代碼可以看出operation2的block執(zhí)行完畢后立刻執(zhí)行completionBlock。
其他方法
**設(shè)置Operation的線程優(yōu)先級(jí)**:我們可以為Operation設(shè)置一個(gè)線程優(yōu)先級(jí)扑浸,即threadPriority烧给。那么執(zhí)行main的時(shí)候,線程優(yōu)先級(jí)就會(huì)調(diào)整到所設(shè)置的線程優(yōu)先級(jí)首装。這個(gè)默認(rèn)值是0.5创夜,我們可以在Operation執(zhí)行前修改它。
operation.threadPriority = 0.1;
注意:如果你重載的start方法仙逻,那么你需要自己來(lái)配置main執(zhí)行時(shí)的線程優(yōu)先級(jí)和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)于多線程編程,還有其他很多功能,這里所寫的只是自己平時(shí)比較常用的檬贰。具體可以查看[官方文檔](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)
最后聲明:
本文所記載的東西都是來(lái)自網(wǎng)上資料或是官方文檔翁涤,如有侵權(quán)桥言,請(qǐng)及時(shí)告訴我,但所有代碼我都親自測(cè)試了一遍葵礼,知識(shí)水平有限号阿,如果有錯(cuò)可隨時(shí)指證,謝謝TХ邸H咏А!希望站在巨人的肩膀上看得更遠(yuǎn)一些届谈。