什么是gcd
https://blog.csdn.net/coolwu123/article/details/28615029
概述
我將分四步來帶大家研究研究程序的并發計算。第一步是主要的串行程序,然后使用GCD把它并行計算化。假設你想順著步驟來嘗試這些程序的話,能夠下載源代碼。注意。別執行imagegcd2.m,這是個反面教材。
。
??imagegcd.zip?(8.4 KB, 79 次)
?
原始程序
我們的程序僅僅是簡單地遍歷~/Pictures然后生成縮略圖。這個程序是個命令行程序,沒有圖形界面(雖然是使用Cocoa開發庫的)。主函數例如以下:
int main(int argc, char **argv){NSAutoreleasePool *outerPool = [NSAutoreleasePool new];NSApplicationLoad();NSString *destination = @"/tmp/imagegcd";[[NSFileManager defaultManager] removeItemAtPath: destination error: NULL];[[NSFileManager defaultManager] createDirectoryAtPath: destinationwithIntermediateDirectories: YESattributes: nilerror: NULL];Start();NSString *dir = [@"~/Pictures" stringByExpandingTildeInPath];NSDirectoryEnumerator *enumerator = [[NSFileManager defaultManager] enumeratorAtPath: dir];int count = 0;for(NSString *path in enumerator){NSAutoreleasePool *innerPool = [NSAutoreleasePool new];if([[[path pathExtension] lowercaseString] isEqual: @"jpg"]){path = [dir stringByAppendingPathComponent: path];NSData *data = [NSData dataWithContentsOfFile: path];if(data){NSData *thumbnailData = ThumbnailDataForData(data);if(thumbnailData){NSString *thumbnailName = [NSString stringWithFormat: @"%d.jpg", count++];NSString *thumbnailPath = [destination stringByAppendingPathComponent: thumbnailName];[thumbnailData writeToFile: thumbnailPath atomically: NO];}}}[innerPool release];}End();[outerPool release];} ?假設你要看到全部的副主函數的話,到文章頂部下載源代碼吧。當前這個程序是imagegcd1.m。
程序中重要的部分都在這里了。.?Start?函數和?End?函數僅僅是簡單的計時函數(內部實現是使用的gettimeofday函數)。
ThumbnailDataForData函數使用Cocoa庫來載入圖片數據生成Image對象。然后將圖片縮小到320×320大小。最后將其編碼為JPEG格式。
?
簡單而天真的并發
乍一看。我們感覺將這個程序并發計算化,非常easy。循環中的每一個迭代器都能夠放入GCD global queue中。
我們能夠使用dispatch queue來等待它們完畢。為了保證每次迭代都會得到唯一的文件名稱數字,我們使用OSAtomicIncrement32來原子操作級別的添加count數:
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);dispatch_group_t group = dispatch_group_create();__block uint32_t count = -1;for(NSString *path in enumerator){dispatch_group_async(group, globalQueue, BlockWithAutoreleasePool(^{if([[[path pathExtension] lowercaseString] isEqual: @"jpg"]){NSString *fullPath = [dir stringByAppendingPathComponent: path];NSData *data = [NSData dataWithContentsOfFile: fullPath];if(data){NSData *thumbnailData = ThumbnailDataForData(data);if(thumbnailData){NSString *thumbnailName = [NSString stringWithFormat: @"%d.jpg",OSAtomicIncrement32(&count;)];NSString *thumbnailPath = [destination stringByAppendingPathComponent: thumbnailName];[thumbnailData writeToFile: thumbnailPath atomically: NO];}}}});}dispatch_group_wait(group, DISPATCH_TIME_FOREVER);這個就是imagegcd2.m。可是,注意,別執行這個程序。有非常大的問題。?
假設你無視我的警告還是執行這個imagegcd2.m了,你如今非常有可能是在重新啟動了電腦后,又打開了我的頁面。。假設你乖乖地沒有執行這個程序的話,執行這個程序發生的情況就是(假設你有非常多非常多圖片在~/Pictures中):電腦沒反應。好久好久都不動,假死了。。
?
問題在哪
問題出在哪?就在于GCD的智能上。GCD將任務放到全局線程池中執行,這個線程池的大小依據系統負載來隨時改變。比如,我的電腦有四核,所以假設我使用GCD載入任務。GCD會為我每一個cpu核創建一個線程。也就是四個線程。假設電腦上其它任務須要進行的話。GCD會降低線程數來使其它任務得以占用cpu資源來完畢。
可是,GCD也能夠添加活動線程數。
它會在其它某個線程堵塞時添加活動線程數。假設如今有四個線程正在執行,突然某個線程要做一個操作,比方,讀文件。這個線程就會等待磁盤響應,此時cpu核心會處于未充分利用的狀態。這是GCD就會發現這個狀態。然后創建還有一個線程來填補這個資源浪費空缺。
如今。想想上面的程序發生了啥?主線程非常迅速地將任務不斷放入global queue中。GCD以一個少量工作線程的狀態開始。然后開始執行任務。這些任務執行了一些非常輕量的工作后。就開始等待磁盤資源,慢得不像話的磁盤資源。
我們別忘記磁盤資源的特性。除非你使用的是SSD或者牛逼的RAID。否則磁盤資源會在競爭的時候變得異常的慢。
。
剛開始的四個任務非常輕松地就同一時候訪問到了磁盤資源,然后開始等待磁盤資源返回。這時GCD發現CPU開始空暇了,它繼續添加工作線程。
然后。這些線程執行很多其它的磁盤讀取任務,然后GCD再創建很多其它的工資線程。。。
可能在某個時間文件讀取任務有完畢的了。如今,線程池中可不止有四個線程,相反。有成百上千個。。。
GCD又會嘗試將工作線程降低(太多使用CPU資源的線程),可是降低線程是由條件的。GCD不能夠將一個正在執行任務的線程殺掉,而且也不能將這種任務暫停。它必須等待這個任務完畢。全部這些情況都導致GCD無法降低工作線程數。
然后全部這上百個線程開始一個個完畢了他們的磁盤讀取工作。
它們開始競爭CPU資源。當然CPU在處理競爭上比磁盤先進多了。
問題在于,這些線程讀完文件后開始編碼這些圖片。假設你有非常多非常多圖片。那么你的內存將開始爆倉。。然后內存耗盡咋辦?虛擬內存啊。虛擬內存是啥,磁盤資源啊。
Oh shit!~
然后進入了一個惡性循環。磁盤資源競爭導致很多其它的線程被創建。這些線程導致很多其它的內存使用,然后內存爆倉導致虛擬內存交換。直至GCD創建了系統規定的線程數上限(可能是512個),而這些線程又沒法被殺掉或暫停。
。
。
這就是使用GCD時。要注意的。GCD能智能地依據CPU情況來調整工作線程數,可是它卻無法監視其它類型的資源狀況。假設你的任務牽涉大量IO或者其它會導致線程block的東西,你須要把握好這個問題。
?
修正
問題的根源來自于磁盤IO,然后導致惡性循環。
攻克了磁盤資源碰撞,就攻克了這個問題。
GCD的custom queue使得這個問題易于解決。
Custom queue是串行的。假設我們創建一個custom queue然后將全部的文件讀寫任務放入這個隊列,磁盤資源的同一時候訪問數會大大降低。資源訪問碰撞就避免了。
蝦米是我們修正后的代碼,使用IO queue(也就是我們創建的custom queue專門用來讀寫磁盤):
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);dispatch_queue_t ioQueue = dispatch_queue_create("com.mikeash.imagegcd.io", NULL);dispatch_group_t group = dispatch_group_create();__block uint32_t count = -1;for(NSString *path in enumerator){if([[[path pathExtension] lowercaseString] isEqual: @"jpg"]){NSString *fullPath = [dir stringByAppendingPathComponent: path];dispatch_group_async(group, ioQueue, BlockWithAutoreleasePool(^{NSData *data = [NSData dataWithContentsOfFile: fullPath];if(data)dispatch_group_async(group, globalQueue, BlockWithAutoreleasePool(^{NSData *thumbnailData = ThumbnailDataForData(data);if(thumbnailData){NSString *thumbnailName = [NSString stringWithFormat: @"%d.jpg",OSAtomicIncrement32(&count;)];NSString *thumbnailPath = [destination stringByAppendingPathComponent: thumbnailName];dispatch_group_async(group, ioQueue, BlockWithAutoreleasePool(^{[thumbnailData writeToFile: thumbnailPath atomically: NO];}));}}));}));}}dispatch_group_wait(group, DISPATCH_TIME_FOREVER);?這個就是我們的?imagegcd3.m.
GCD使得我們非常easy就將任務的不同部分放入同樣的隊列中去(簡單地嵌套一下dispatch)。這次我們的程序將會表現地非常好。。
。我是說多數情況。。。。
問題在于任務中的不同部分不是同步的。導致了整個程序的不穩定。
我們的新程序的整個流程例如以下:
Main Thread IO Queue Concurrent Queuefind paths ------> read -----------> process...write <----------- process圖中的箭頭是非堵塞的,而且會簡單地將內存中的對象進行緩沖。
?
?如今假設一個機器的磁盤足夠快,快到比CPU處理任務(也就是圖片處理)要快。
事實上不難想象:雖然CPU的動作非常快,可是它的工作更繁重,解碼、壓縮、編碼。從磁盤讀取的數據開始填滿IO queue,數據會占用內存,非常可能越占越多(假設你的~/Pictures中有非常多非常多圖片的話)。
然后你就會內存爆倉,然后開始虛擬內存交換。
。。又來了。
。
這就會像第一次一樣導致惡性循環。一旦不論什么東西導致工作線程堵塞。GCD就會創建很多其它的線程,這個線程執行的任務又會占用內存(從磁盤讀取的數據),然后又開始交換內存。
。
結果:這個程序要么就是執行地非常順暢,要么就是非常低效。
注意假設磁盤速度比較慢的話。這個問題依然會出現。由于縮略圖會被緩沖在內存里,只是這個問題導致的低效比較不easy出現,由于縮略圖占的內存少得多。
?
真正的修復
由于上一次我們的嘗試出現的問題在于沒有同步不同部分的操作,所以讓我寫出同步的代碼。最簡單的方法就是使用信號量來限制同一時候執行的任務數量。
那么,我們須要限制為多少呢?
顯然我們須要依據CPU的核數來限制這個量,我們又想馬兒好又想馬兒不吃草。我們就設置為cpu核數的兩倍吧。只是這里僅僅是簡單地這樣處理。GCD的作用之中的一個就是讓我們不用關心操作系統的內部信息(比方cpu數),如今又來讀取cpu核數。確實不太妙。或許我們在實際應用中,能夠依據其它需求來定義這個限制量。
如今我們的主循環代碼就是這樣了:
dispatch_queue_t ioQueue = dispatch_queue_create("com.mikeash.imagegcd.io", NULL);int cpuCount = [[NSProcessInfo processInfo] processorCount];dispatch_semaphore_t jobSemaphore = dispatch_semaphore_create(cpuCount * 2);dispatch_group_t group = dispatch_group_create();__block uint32_t count = -1;for(NSString *path in enumerator){WithAutoreleasePool(^{if([[[path pathExtension] lowercaseString] isEqual: @"jpg"]){NSString *fullPath = [dir stringByAppendingPathComponent: path];dispatch_semaphore_wait(jobSemaphore, DISPATCH_TIME_FOREVER);dispatch_group_async(group, ioQueue, BlockWithAutoreleasePool(^{NSData *data = [NSData dataWithContentsOfFile: fullPath];dispatch_group_async(group, globalQueue, BlockWithAutoreleasePool(^{NSData *thumbnailData = ThumbnailDataForData(data);if(thumbnailData){NSString *thumbnailName = [NSString stringWithFormat: @"%d.jpg",OSAtomicIncrement32(&count;)];NSString *thumbnailPath = [destination stringByAppendingPathComponent: thumbnailName];dispatch_group_async(group, ioQueue, BlockWithAutoreleasePool(^{[thumbnailData writeToFile: thumbnailPath atomically: NO];dispatch_semaphore_signal(jobSemaphore);}));}elsedispatch_semaphore_signal(jobSemaphore);}));}));}});}dispatch_group_wait(group, DISPATCH_TIME_FOREVER);終于我們寫出了一個能平滑執行且又高速處理的程序。
?
基準測試
我測試了一些執行時間,對7913張圖片:
?
程序處理時間 (秒)| imagegcd1.m | 984 |
| imagegcd2.m | 沒執行,這個還是別執行了 |
| imagegcd3.m | 300 |
| imagegcd4.m | 279 |
?
?
注意,由于我比較懶。
所以我在執行這些測試的時候,沒有關閉電腦上的其它程序。。
。嚴格的進行對比的話,實在是太蛋疼了。。
所以這個數值我們僅僅是參考一下。
比較有意思的是。3和4的執行狀況幾乎相同,大概是由于我電腦有15g可用內存吧。。。內存比較小的話,這個imagegcd3應該跑的非常吃力。由于我發現它使用最多的時候,占用了10g內存。而4的話。沒有占多少內存。
結論
GCD是個比較范特西的技術,能夠辦到非常多事兒,可是它不能為你辦全部的事兒。
所以,對于進行IO操作而且可能會使用大量內存的任務。我們必須細致斟酌。
當然,即使這樣,GCD還是為我們提供了簡單有效的方法來進行并發計算。
轉載于:https://www.cnblogs.com/ldxsuanfa/p/10555387.html
總結
- 上一篇: 各种梯度下降 bgd sgd mbgd
- 下一篇: VS设置DLL所在的调试目录