茶余飯后的多線(xiàn)程
故事是這樣的:
艷:戴戴,我最近要面試抹锄,但是我都不知道會(huì)面試什么東西逆瑞!
戴:多線(xiàn)程荠藤,內(nèi)存,源碼获高,runtime等哈肖!
艷不爽:我也知道是這些,可是具體面試啥呢念秧?
戴:比如多線(xiàn)程淤井!額...
沉思片刻...
額 ....
艷:切。
戴:額... 多線(xiàn)程(英語(yǔ):multithreading)摊趾,是指從軟件或者硬件上實(shí)現(xiàn)多個(gè)線(xiàn)程并發(fā)執(zhí)行的技術(shù)币狠。具有多線(xiàn)程能力的計(jì)算機(jī)因有硬件支持而能夠在同一時(shí)間執(zhí)行多于一個(gè)線(xiàn)程,進(jìn)而提升整體處理性能砾层。
艷:能不能具體點(diǎn)漩绵!
戴:這樣!假如我現(xiàn)在想做一個(gè)下載的功能肛炮,但是呢止吐,我肯定不能在主線(xiàn)程上下載對(duì)不對(duì)?
嗯...
那我就需要開(kāi)個(gè)子線(xiàn)程
異步下載
dispatch_queue_t queue = dispatch_queue_create("1", nil);
dispatch_async(queue, ^{
NSLog(@"download...");
});
就像這樣侨糟,我創(chuàng)建個(gè)線(xiàn)程讓他異步執(zhí)行就好了碍扔。
艷:但是這樣下載完了,主線(xiàn)程根本不知道粟害!
戴:可以這樣
異步下載之后回調(diào)主線(xiàn)程
dispatch_queue_t queue1 = dispatch_queue_create("1", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue1, ^{
//這里假設(shè)sleep是在download
sleep(rand()%10);
NSLog(@"download...");
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"dowload complete!");
});
});
這里有一個(gè)要注意一下哈蕴忆!
DISPATCH_QUEUE_CONCURRENT
:并行隊(duì)列,以先進(jìn)先出的方式悲幅,并發(fā)調(diào)度隊(duì)列中的任務(wù)執(zhí)行
如果當(dāng)前調(diào)度的任務(wù)是同步執(zhí)行的套鹅,會(huì)等待任務(wù)執(zhí)行完成后,再調(diào)度后續(xù)的任務(wù)
如果當(dāng)前調(diào)度的任務(wù)是異步執(zhí)行的汰具,同時(shí)底層線(xiàn)程池有可用的線(xiàn)程資源卓鹿,會(huì)再新的線(xiàn)程調(diào)度后續(xù)任務(wù)的執(zhí)行DISPATCH_QUEUE_SERIAL
:串行隊(duì)列,永遠(yuǎn)是任務(wù)一個(gè)一個(gè)的執(zhí)行
艷:那我明白了留荔,如果我想要同時(shí)下載5個(gè)文件我就可以這樣
創(chuàng)建并行隊(duì)列吟孙,多任務(wù)同步執(zhí)行
for (NSInteger i = 0 ; i < 5; i ++ ) {
dispatch_queue_t dqueue = dispatch_queue_create("1", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(dqueue, ^{
//這里假設(shè)sleep是在download
sleep(rand()%10);
NSLog(@"dowload %ld",i);
});
}
戴:沒(méi)錯(cuò)的,可是如果有100個(gè)聚蝶,1000個(gè)文件要下載呢杰妓?
艷:那我就for
循環(huán)100
次,1000
...好像不大對(duì)哦碘勉?
戴:因?yàn)樵O(shè)備性能的問(wèn)題巷挥,100
,1000
循環(huán)創(chuàng)建,這種方式明顯不可取验靡,那也就是我們需要讓他每次并行的數(shù)量有一個(gè)控制倍宾。這時(shí)候我們會(huì)聯(lián)想到一個(gè)名詞叫做信號(hào)量雏节!
信號(hào)量:信號(hào)量(Semaphore),有時(shí)被稱(chēng)為信號(hào)燈高职,是在多線(xiàn)程環(huán)境下使用的一種設(shè)施钩乍,是可以用來(lái)保證兩個(gè)或多個(gè)關(guān)鍵代碼段不被并發(fā)調(diào)用。
通過(guò)信號(hào)量控制怔锌,同時(shí)只有五個(gè)任務(wù)在下載
dispatch_semaphore_t semaphore = dispatch_semaphore_create(5);
dispatch_queue_t ddqueue = dispatch_queue_create("1", DISPATCH_QUEUE_CONCURRENT);
for (NSInteger i = 0; i < 100; i ++) {
dispatch_async(ddqueue, ^{
sleep(rand()%10);
NSLog(@"dowload %ld",i);
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
}
這里我解釋一下這個(gè)信號(hào)量怎么運(yùn)作的寥粹。
-
dispatch_semaphore_create
:創(chuàng)建信號(hào)量,并且傳入的參數(shù)是個(gè)long
類(lèi)型产禾,意義就是可以同時(shí)存在多少資源數(shù)排作,這里可以理解為線(xiàn)程 -
dispatch_semaphore_signal
:發(fā)送信號(hào)牵啦,會(huì)使當(dāng)前信號(hào)量+1
-
dispatch_semaphore_wait
:減低信號(hào)亚情,如果當(dāng)前信號(hào)量大于1,會(huì)使當(dāng)前信號(hào)量-1
,如果等于0 進(jìn)入等待哈雏。后面跟的時(shí)間就是等待的時(shí)長(zhǎng)了楞件。
那么上面的代碼意思就是我同時(shí)開(kāi)始下載的任務(wù)只有五個(gè),有一個(gè)下載完就會(huì)進(jìn)入下一個(gè)了裳瘪。
艷:嗯...我如果想給任務(wù)排個(gè)序土浸,就是知道完成的順序怎么辦呢?
戴:這就涉及到多線(xiàn)程里面比較常用的鎖了彭羹。
多線(xiàn)程中的鎖
NSLock *lock = [[NSLock alloc] init];
__block NSInteger index = 0;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(5);
dispatch_queue_t ddqueue = dispatch_queue_create("1", DISPATCH_QUEUE_CONCURRENT);
for (NSInteger i = 0; i < 100; i ++) {
dispatch_async(ddqueue, ^{
sleep(rand()%10);
[lock lock];
NSLog(@"the index of %ld: %ld",index,i);
index ++;
[lock unlock];
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
這樣就會(huì)有順序了黄伊,對(duì)index
進(jìn)行讀寫(xiě)的時(shí)候進(jìn)行加鎖,防止index
被其他線(xiàn)程給改了派殷。
艷:那么如果有些任務(wù)要是失敗了还最,我是不是應(yīng)該給他重新下,這個(gè)怎么做毡惜?
戴:有兩種方案:
- 方案1:如果不成功就重復(fù)調(diào)用當(dāng)前線(xiàn)程拓轻,也就是如果不成功就重復(fù)跑下載線(xiàn)程,讓他占著資源经伙。
- 方案2:如果不成功扶叉,就把當(dāng)前任務(wù)丟到任務(wù)尾部,等其他任務(wù)下載完了之后再去執(zhí)行這個(gè)任務(wù)帕膜。
大多下載工具的實(shí)現(xiàn)是 方案1 + 方案2 的結(jié)合枣氧。
為了簡(jiǎn)單,我就只展示給你看下方案2哈垮刹,因?yàn)榉桨敢淮锿蹋嗑€(xiàn)程關(guān)系不大。
這里我用一個(gè)array來(lái)代表一些下載任務(wù)吧危纫。
多任務(wù)下載的下載失敗處理
NSInteger totalCount = 100;
NSLock *lock = [[NSLock alloc] init];
NSMutableArray *needDownloadarray = [[NSMutableArray alloc] init];
for (int i =0 ; i < totalCount; i ++) {
[array addObject:@1];
}
__block NSInteger index = 0;
__block NSInteger completeCount = 0;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
dispatch_queue_t ddqueue = dispatch_queue_create("1", DISPATCH_QUEUE_CONCURRENT);
BOOL stop = false;
while (completeCount != totalCount && !stop) {
if (needDownloadarray.count > 0) {
[lock lock];
[needDownloadarray removeObject:[needDowanloadarray firstObject]];
[lock unlock];
dispatch_async(ddqueue, ^{
NSInteger randomcount = rand()%10;
sleep((unsigned int)randomcount / 5);
[lock lock];
index ++;
if (randomcount > 8) {
[needDownloadarray addObject:@1];
NSLog(@"the index of %ld , download failed",index);
} else {
completeCount ++ ;
NSLog(@"the index of %ld , complete is %ld",index,completeCount);
}
[lock unlock];
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
}
NSLog(@"download completed!");
如果隨機(jī)數(shù)大于8宗挥,我們假設(shè)他失敗吧乌庶!哈哈!
- 如果下載失敗契耿,就把任務(wù)丟到
needDownloadarray
最后面
戴:這下明白了不瞒大!
艷:嗯
戴:其實(shí)嘛,這種多個(gè)文件下載搪桂,直接打個(gè)包下載就好了透敌,下下來(lái)解壓一下就完了,文件太大給他搞個(gè)斷點(diǎn)續(xù)傳踢械,就美滋滋了酗电。
艷:那你不早說(shuō)!
戴:你不是想知道多線(xiàn)程么内列?
艷:切撵术!那你跟我說(shuō)說(shuō)斷點(diǎn)續(xù)傳!
戴:我地乖乖话瞧,不早了嫩与,洗洗睡吧!明天...明天...
注意
- 信號(hào)量里面的
wait
,signal
要配對(duì)使用交排,不然也會(huì)造成死鎖划滋。 - 多線(xiàn)程訪問(wèn)同一塊內(nèi)存的時(shí)候,盡量加鎖
- 加鎖實(shí)際上會(huì)增加調(diào)度消耗埃篓,并不是鎖越多越好处坪,怎么把鎖的調(diào)度變得合理是一門(mén)課題。
- 還有很多注意大家自己發(fā)掘