自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

GCD實戰(zhàn)二:資源競爭

移動開發(fā) iOS
GCD是個比較范特西的技術(shù),可以辦到很多事兒,但是它不能為你辦所有的事兒。所以,對于進(jìn)行IO操作并且可能會使用大量內(nèi)存的任務(wù),我們必須仔細(xì)斟酌。當(dāng)然,即使這樣,GCD還是為我們提供了簡單有效的方法來進(jìn)行并發(fā)計算。

[[77421]]

概述

我將分四步來帶大家研究研究程序的并發(fā)計算。第一步是基本的串行程序,然后使用GCD把它并行計算化。如果你想順著步驟來嘗試這些程序的話,可以下載源碼。注意,別運行imagegcd2.m,這是個反面教材。

源碼下載:http://down.51cto.com/data/872222

原始程序

我們的程序只是簡單地遍歷~/Pictures然后生成縮略圖。這個程序是個命令行程序,沒有圖形界面(盡管是使用Cocoa開發(fā)庫的),主函數(shù)如下:

  1. int main(int argc, char **argv) 
  2.     NSAutoreleasePool *outerPool = [NSAutoreleasePool new]; 
  3.     NSApplicationLoad(); 
  4.     NSString *destination = @"/tmp/imagegcd"
  5.     [[NSFileManager defaultManager] removeItemAtPath: destination error: NULL]; 
  6.     [[NSFileManager defaultManager] createDirectoryAtPath: destination 
  7. IntermediateDirectories: YES 
  8.                                     attributes: nil 
  9.                                     error: NULL]; 
  10.     Start(); 
  11.     NSString *dir = [@"~/Pictures" stringByExpandingTildeInPath]; 
  12.     NSDirectoryEnumerator *enumerator = [[NSFileManager defaultManager] enumeratorAtPath: dir]; 
  13.     int count = 0; 
  14.     for(NSString *path in enumerator) 
  15.     { 
  16.         NSAutoreleasePool *innerPool = [NSAutoreleasePool new]; 
  17.         if([[[path pathExtension] lowercaseString] isEqual: @"jpg"]) 
  18.         { 
  19.             path = [dir stringByAppendingPathComponent: path]; 
  20.              
  21.             NSData *data = [NSData dataWithContentsOfFile: path]; 
  22.             if(data) 
  23.             { 
  24.                 NSData *thumbnailData = ThumbnailDataForData(data); 
  25.                 if(thumbnailData) 
  26.                 { 
  27.                     NSString *thumbnailName = [NSString stringWithFormat: @"%d.jpg", count++]; 
  28.                     NSString *thumbnailPath = [destination stringByAppendingPathComponent: thumbnailName]; 
  29.                     [thumbnailData writeToFile: thumbnailPath atomically: NO]; 
  30.                 } 
  31.             } 
  32.         } 
  33.         [innerPool release]; 
  34.     } 
  35.     End(); 
  36.     [outerPool release]; 

如果你要看到所有的副主函數(shù)的話,到文章頂部下載源代碼吧。當(dāng)前這個程序是imagegcd1.m。程序中重要的部分都在這里了。. Start 函數(shù)和 End 函數(shù)只是簡單的計時函數(shù)(內(nèi)部實現(xiàn)是使用的gettimeofday函數(shù))。ThumbnailDataForData函數(shù)使用Cocoa庫來加載圖片數(shù)據(jù)生成Image對象,然后將圖片縮小到320×320大小,最后將其編碼為JPEG格式。

簡單而天真的并發(fā)

乍一看,我們感覺將這個程序并發(fā)計算化,很容易。循環(huán)中的每個迭代器都可以放入GCD global queue中。我們可以使用dispatch queue來等待它們完成。為了保證每次迭代都會得到唯一的文件名數(shù)字,我們使用OSAtomicIncrement32來原子操作級別的增加count數(shù):

  1.     dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0); 
  2.     dispatch_group_t group = dispatch_group_create(); 
  3.     __block uint32_t count = -1; 
  4.     for(NSString *path in enumerator) 
  5.     { 
  6.         dispatch_group_async(group, globalQueue, BlockWithAutoreleasePool(^{ 
  7.             if([[[path pathExtension] lowercaseString] isEqual: @"jpg"]) 
  8.             { 
  9.                 NSString *fullPath = [dir stringByAppendingPathComponent: path]; 
  10.                  
  11.                 NSData *data = [NSData dataWithContentsOfFile: fullPath]; 
  12.                 if(data) 
  13.                 { 
  14.                     NSData *thumbnailData = ThumbnailDataForData(data); 
  15.                     if(thumbnailData) 
  16.                     { 
  17.                         NSString *thumbnailName = [NSString stringWithFormat: @"%d.jpg"
  18. OSAtomicIncrement32(&count;)]; 
  19.                         NSString *thumbnailPath = [destination stringByAppendingPathComponent: thumbnailName]; 
  20.                         [thumbnailData writeToFile: thumbnailPath atomically: NO]; 
  21.                     } 
  22.                 } 
  23.             } 
  24.         }); 
  25.     } 
  26.     dispatch_group_wait(group, DISPATCH_TIME_FOREVER); 

這個就是imagegcd2.m,但是,注意,別運行這個程序,有很大的問題。 

如果你無視我的警告還是運行這個imagegcd2.m了,你現(xiàn)在很有可能是在重啟了電腦后,又打開了我的頁面。。如果你乖乖地沒有運行這個程序的話,運行這個程序發(fā)生的情況就是(如果你有很多很多圖片在~/Pictures中):電腦沒反應(yīng),好久好久都不動,假死了。。

問題在哪

問題出在哪?就在于GCD的智能上。GCD將任務(wù)放到全局線程池中運行,這個線程池的大小根據(jù)系統(tǒng)負(fù)載來隨時改變。例如,我的電腦有四核,所以如果我使用GCD加載任務(wù),GCD會為我每個cpu核創(chuàng)建一個線程,也就是四個線程。如果電腦上其他任務(wù)需要進(jìn)行的話,GCD會減少線程數(shù)來使其他任務(wù)得以占用cpu資源來完成。

但是,GCD也可以增加活動線程數(shù)。它會在其他某個線程阻塞時增加活動線程數(shù)。假設(shè)現(xiàn)在有四個線程正在運行,突然某個線程要做一個操作,比如,讀文件,這個線程就會等待磁盤響應(yīng),此時cpu核心會處于未充分利用的狀態(tài)。這是GCD就會發(fā)現(xiàn)這個狀態(tài),然后創(chuàng)建另一個線程來填補這個資源浪費空缺。

現(xiàn)在,想想上面的程序發(fā)生了啥?主線程非常迅速地將任務(wù)不斷放入global queue中。GCD以一個少量工作線程的狀態(tài)開始,然后開始執(zhí)行任務(wù)。這些任務(wù)執(zhí)行了一些很輕量的工作后,就開始等待磁盤資源,慢得不像話的磁盤資源。

我們別忘記磁盤資源的特性,除非你使用的是SSD或者牛逼的RAID,否則磁盤資源會在競爭的時候變得異常的慢。。

剛開始的四個任務(wù)很輕松地就同時訪問到了磁盤資源,然后開始等待磁盤資源返回。這時GCD發(fā)現(xiàn)CPU開始空閑了,它繼續(xù)增加工作線程。然后,這些線程執(zhí)行更多的磁盤讀取任務(wù),然后GCD再創(chuàng)建更多的工資線程。。。

可能在某個時間文件讀取任務(wù)有完成的了?,F(xiàn)在,線程池中可不止有四個線程,相反,有成百上千個。。。GCD又會嘗試將工作線程減少(太多使用CPU資源的線程),但是減少線程是由條件的,GCD不可以將一個正在執(zhí)行任務(wù)的線程殺掉,并且也不能將這樣的任務(wù)暫停。它必須等待這個任務(wù)完成。所有這些情況都導(dǎo)致GCD無法減少工作線程數(shù)。

然后所有這上百個線程開始一個個完成了他們的磁盤讀取工作。它們開始競爭CPU資源,當(dāng)然CPU在處理競爭上比磁盤先進(jìn)多了。問題在于,這些線程讀完文件后開始編碼這些圖片,如果你有很多很多圖片,那么你的內(nèi)存將開始爆倉。。然后內(nèi)存耗盡咋辦?虛擬內(nèi)存啊,虛擬內(nèi)存是啥,磁盤資源啊。Oh shit!~

然后進(jìn)入了一個惡性循環(huán),磁盤資源競爭導(dǎo)致更多的線程被創(chuàng)建,這些線程導(dǎo)致更多的內(nèi)存使用,然后內(nèi)存爆倉導(dǎo)致虛擬內(nèi)存交換,直至GCD創(chuàng)建了系統(tǒng)規(guī)定的線程數(shù)上限(可能是512個),而這些線程又沒法被殺掉或暫停。。。

這就是使用GCD時,要注意的。GCD能智能地根據(jù)CPU情況來調(diào)整工作線程數(shù),但是它卻無法監(jiān)視其他類型的資源狀況。如果你的任務(wù)牽涉大量IO或者其他會導(dǎo)致線程block的東西,你需要把握好這個問題。

修正
問題的根源來自于磁盤IO,然后導(dǎo)致惡性循環(huán)。解決了磁盤資源碰撞,就解決了這個問題。

GCD的custom queue使得這個問題易于解決。Custom queue是串行的。如果我們創(chuàng)建一個custom queue然后將所有的文件讀寫任務(wù)放入這個隊列,磁盤資源的同時訪問數(shù)會大大降低,資源訪問碰撞就避免了。

蝦米是我們修正后的代碼,使用IO queue(也就是我們創(chuàng)建的custom queue專門用來讀寫磁盤):

  1. dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0); 
  2. dispatch_queue_t ioQueue = dispatch_queue_create("com.mikeash.imagegcd.io", NULL); 
  3. dispatch_group_t group = dispatch_group_create(); 
  4. __block uint32_t count = -1; 
  5. for(NSString *path in enumerator) 
  6.     if([[[path pathExtension] lowercaseString] isEqual: @"jpg"]) 
  7.     { 
  8.         NSString *fullPath = [dir stringByAppendingPathComponent: path]; 
  9.          
  10.         dispatch_group_async(group, ioQueue, BlockWithAutoreleasePool(^{ 
  11.             NSData *data = [NSData dataWithContentsOfFile: fullPath]; 
  12.             if(data) 
  13.                 dispatch_group_async(group, globalQueue, BlockWithAutoreleasePool(^{ 
  14.                     NSData *thumbnailData = ThumbnailDataForData(data); 
  15.                     if(thumbnailData) 
  16.                     { 
  17.                         NSString *thumbnailName = [NSString stringWithFormat: @"%d.jpg"
  18.                                                    OSAtomicIncrement32(&count;)]; 
  19.                         NSString *thumbnailPath = [destination stringByAppendingPathComponent: thumbnailName]; 
  20.                         dispatch_group_async(group, ioQueue, BlockWithAutoreleasePool(^{ 
  21.                             [thumbnailData writeToFile: thumbnailPath atomically: NO]; 
  22.                         })); 
  23.                     } 
  24.                 })); 
  25.         })); 
  26.     } 
  27. dispatch_group_wait(group, DISPATCH_TIME_FOREVER); 

這個就是我們的 imagegcd3.m.

GCD使得我們很容易就將任務(wù)的不同部分放入相同的隊列中去(簡單地嵌套一下dispatch)。這次我們的程序?qū)憩F(xiàn)地很好。。。我是說多數(shù)情況。。。。

問題在于任務(wù)中的不同部分不是同步的,導(dǎo)致了整個程序的不穩(wěn)定。我們的新程序的整個流程如下:

    Main Thread          IO Queue            Concurrent Queue
    
    find paths  ------>  read  ----------->  process
                                             ...
                         write <-----------  process

圖中的箭頭是非阻塞的,并且會簡單地將內(nèi)存中的對象進(jìn)行緩沖。

 現(xiàn)在假設(shè)一個機器的磁盤足夠快,快到比CPU處理任務(wù)(也就是圖片處理)要快。其實不難想象:雖然CPU的動作很快,但是它的工作更繁重,解碼、壓縮、編碼。從磁盤讀取的數(shù)據(jù)開始填滿IO queue,數(shù)據(jù)會占用內(nèi)存,很可能越占越多(如果你的~/Pictures中有很多很多圖片的話)。

然后你就會內(nèi)存爆倉,然后開始虛擬內(nèi)存交換。。。又來了。。

這就會像第一次一樣導(dǎo)致惡性循環(huán)。一旦任何東西導(dǎo)致工作線程阻塞,GCD就會創(chuàng)建更多的線程,這個線程執(zhí)行的任務(wù)又會占用內(nèi)存(從磁盤讀取的數(shù)據(jù)),然后又開始交換內(nèi)存。。

結(jié)果:這個程序要么就是運行地很順暢,要么就是很低效。

注意如果磁盤速度比較慢的話,這個問題依舊會出現(xiàn),因為縮略圖會被緩沖在內(nèi)存里,不過這個問題導(dǎo)致的低效比較不容易出現(xiàn),因為縮略圖占的內(nèi)存少得多。

真正的修復(fù)

由于上一次我們的嘗試出現(xiàn)的問題在于沒有同步不同部分的操作,所以讓我寫出同步的代碼。最簡單的方法就是使用信號量來限制同時執(zhí)行的任務(wù)數(shù)量。

那么,我們需要限制為多少呢?

顯然我們需要根據(jù)CPU的核數(shù)來限制這個量,我們又想馬兒好又想馬兒不吃草,我們就設(shè)置為cpu核數(shù)的兩倍吧。不過這里只是簡單地這樣處理,GCD的作用之一就是讓我們不用關(guān)心操作系統(tǒng)的內(nèi)部信息(比如cpu數(shù)),現(xiàn)在又來讀取cpu核數(shù),確實不太妙。也許我們在實際應(yīng)用中,可以根據(jù)其他需求來定義這個限制量。

現(xiàn)在我們的主循環(huán)代碼就是這樣了:

  1. dispatch_queue_t ioQueue = dispatch_queue_create("com.mikeash.imagegcd.io", NULL); 
  2.  
  3. int cpuCount = [[NSProcessInfo processInfo] processorCount]; 
  4. dispatch_semaphore_t jobSemaphore = dispatch_semaphore_create(cpuCount * 2); 
  5.  
  6. dispatch_group_t group = dispatch_group_create(); 
  7. __block uint32_t count = -1; 
  8. for(NSString *path in enumerator) 
  9.     WithAutoreleasePool(^{ 
  10.         if([[[path pathExtension] lowercaseString] isEqual: @"jpg"]) 
  11.         { 
  12.             NSString *fullPath = [dir stringByAppendingPathComponent: path]; 
  13.              
  14.             dispatch_semaphore_wait(jobSemaphore, DISPATCH_TIME_FOREVER); 
  15.          
  16.             dispatch_group_async(group, ioQueue, BlockWithAutoreleasePool(^{ 
  17.                 NSData *data = [NSData dataWithContentsOfFile: fullPath]; 
  18.                 dispatch_group_async(group, globalQueue, BlockWithAutoreleasePool(^{ 
  19.                     NSData *thumbnailData = ThumbnailDataForData(data); 
  20.                     if(thumbnailData) 
  21.                     { 
  22.                         NSString *thumbnailName = [NSString stringWithFormat: @"%d.jpg"
  23.                                                    OSAtomicIncrement32(&count;)]; 
  24.                         NSString *thumbnailPath = [destination stringByAppendingPathComponent: thumbnailName]; 
  25.                         dispatch_group_async(group, ioQueue, BlockWithAutoreleasePool(^{ 
  26.                             [thumbnailData writeToFile: thumbnailPath atomically: NO]; 
  27.                             dispatch_semaphore_signal(jobSemaphore); 
  28.                         })); 
  29.                     } 
  30.                     else 
  31. atch_semaphore_signal(jobSemaphore); 
  32.                 })); 
  33.             })); 
  34.         } 
  35.     }); 
  36. dispatch_group_wait(group, DISPATCH_TIME_FOREVER); 

最終我們寫出了一個能平滑運行且又快速處理的程序。

基準(zhǔn)測試

我測試了一些運行時間,對7913張圖片:

程序 處理時間 (秒)
imagegcd1.m 984
imagegcd2.m 沒運行,這個還是別運行了
imagegcd3.m 300
imagegcd4.m 279

注意,因為我比較懶。所以我在運行這些測試的時候,沒有關(guān)閉電腦上的其他程序。。。嚴(yán)格的進(jìn)行對照的話,實在是太蛋疼了。。

所以這個數(shù)值我們只是參考一下。

比較有意思的是,3和4的執(zhí)行狀況差不多,大概是因為我電腦有15g可用內(nèi)存吧。。。內(nèi)存比較小的話,這個imagegcd3應(yīng)該跑的很吃力,因為我發(fā)現(xiàn)它使用最多的時候,占用了10g內(nèi)存。而4的話,沒有占多少內(nèi)存。

結(jié)論

GCD是個比較范特西的技術(shù),可以辦到很多事兒,但是它不能為你辦所有的事兒。所以,對于進(jìn)行IO操作并且可能會使用大量內(nèi)存的任務(wù),我們必須仔細(xì)斟酌。當(dāng)然,即使這樣,GCD還是為我們提供了簡單有效的方法來進(jìn)行并發(fā)計算。

責(zé)任編輯:閆佳明 來源: dreamingwish
相關(guān)推薦

2011-06-07 16:54:34

SEO

2013-07-15 16:00:59

2013-07-15 16:39:41

iOS多線程GCD實戰(zhàn)串行隊列實現(xiàn)預(yù)加載

2013-07-15 15:23:03

iOS多線程GCD

2013-07-15 16:28:15

iOS多線程GCD介紹Dispatch Qu

2024-01-02 11:13:27

Java死鎖

2023-12-07 12:32:57

Java死鎖線程

2013-07-15 16:18:08

2013-11-29 10:24:52

Cluster設(shè)計資源池

2013-12-02 10:34:32

虛擬化實戰(zhàn)Cluster

2012-02-15 10:34:29

JavaJava Socket

2012-02-24 12:06:09

光纖H3C交換機

2017-12-20 09:35:25

Python爬蟲百度云資源

2023-05-05 16:05:26

設(shè)備內(nèi)核移植鴻蒙

2013-05-27 09:52:26

虛擬化存儲虛擬化應(yīng)用

2010-06-09 13:02:29

MySQL啟用二進(jìn)制日

2019-01-10 10:02:32

機器學(xué)習(xí)數(shù)據(jù)人工智能

2009-12-25 16:45:57

WPF競爭

2016-08-25 21:12:31

微服務(wù)架構(gòu)發(fā)布

2013-07-15 15:51:32

iOS多線程GCD基本概念Dispatch Qu
點贊
收藏

51CTO技術(shù)棧公眾號