什么是gcd
概述
我将分四步来带大家研究研究程序的并发计算。
第一步是主要的串行程序,然后使用GCD把它并行计算化。假设你想顺着步骤来尝试这些程序的话,能够下载源代码。
注意。别执行imagegcd2.m,这是个反面教材。。
dl_id=8" style="color:#0000FF;font-weight:normal;">imagegcd.zip
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: destination
withIntermediateDirectories: YES
attributes: nil
error: 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 Queue find 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);
}));
}
else
dispatch_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还是为我们提供了简单有效的方法来进行并发计算。
什么是gcd的更多相关文章
- Objective-C三种定时器CADisplayLink / NSTimer / GCD的使用
OC中的三种定时器:CADisplayLink.NSTimer.GCD 我们先来看看CADiskplayLink, 点进头文件里面看看, 用注释来说明下 @interface CADisplayLin ...
- iOS 多线程之GCD的使用
在iOS开发中,遇到耗时操作,我们经常用到多线程技术.Grand Central Dispatch (GCD)是Apple开发的一个多核编程的解决方法,只需定义想要执行的任务,然后添加到适当的调度队列 ...
- 【swift】BlockOperation和GCD实用代码块
//BlockOperation // // ViewController.swift import UIKit class ViewController: UIViewController { @I ...
- 修改版: 小伙,多线程(GCD)看我就够了,骗你没好处!
多线程(英语:multithreading),是指从软件或者硬件上实现多个线程并发执行的技术.具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能.具有这种能力的系 ...
- GCD的相关函数使用
GCD 是iOS多线程实现方案之一,非常常用 英文翻译过来就是伟大的中枢调度器,也有人戏称为是牛逼的中枢调度器 是苹果公司为多核的并行运算提出的解决方案 1.一次性函数 dispatch_once 顾 ...
- hdu1695 GCD(莫比乌斯反演)
题意:求(1,b)区间和(1,d)区间里面gcd(x, y) = k的数的对数(1<=x<=b , 1<= y <= d). 知识点: 莫比乌斯反演/*12*/ 线性筛求莫比乌 ...
- hdu2588 GCD (欧拉函数)
GCD 题意:输入N,M(2<=N<=1000000000, 1<=M<=N), 设1<=X<=N,求使gcd(X,N)>=M的X的个数. (文末有题) 知 ...
- BZOJ 2820: YY的GCD [莫比乌斯反演]【学习笔记】
2820: YY的GCD Time Limit: 10 Sec Memory Limit: 512 MBSubmit: 1624 Solved: 853[Submit][Status][Discu ...
- BZOJ 2818: Gcd [欧拉函数 质数 线性筛]【学习笔记】
2818: Gcd Time Limit: 10 Sec Memory Limit: 256 MBSubmit: 4436 Solved: 1957[Submit][Status][Discuss ...
- GCD总结
//用block只有两种:同步执行/异步执行(参数1:队列;参数二:任务) dispatch_async(dispatch_get_global_queue(0, 0),^{ });//异步在新的线程 ...
随机推荐
- 计蒜客 28449.算个欧拉函数给大家助助兴-大数的因子个数 (HDU5649.DZY Loves Sorting) ( ACM训练联盟周赛 G)
ACM训练联盟周赛 这一场有几个数据结构的题,但是自己太菜,不会树套树,带插入的区间第K小-替罪羊套函数式线段树, 先立个flag,BZOJ3065: 带插入区间K小值 计蒜客 Zeratul与Xor ...
- Walls and Gates -- LeetCode
You are given a m x n 2D grid initialized with these three possible values. -1 - A wall or an obstac ...
- Closest Binary Search Tree Value -- LeetCode
Given a non-empty binary search tree and a target value, find the value in the BST that is closest t ...
- POJ 3977:Subset(折半枚举+二分)
[题目链接] http://poj.org/problem?id=3977 [题目大意] 在n个数(n<36)中选取一些数,使得其和的绝对值最小. [题解] 因为枚举所有数选或者不选,复杂度太高 ...
- php的一些语法
命名空间: 一个类为App/Http/Controllers/Controller,则该类的命名空间为App/Http/Controllers,可以通过use关键字导入该类,也可以导入命名空间,但是该 ...
- SQL表操作习题1
建表
- JavaSript模块规范 - AMD规范与CMD规范介绍 (转)
JavaSript模块化 在了解AMD,CMD规范前,还是需要先来简单地了解下什么是模块化,模块化开发? 模块化是指在解决某一个复杂问题或者一系列的杂糅问题时,依照一种分类的思维把问题 ...
- 【Mybatis】mybatis查询报错org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 'areaName' in 'class java.lang.String'
mybatis查询报错: Caused by: org.apache.ibatis.reflection.ReflectionException: There is no getter for pro ...
- The newly created daemon process has a different context than expected
Error: The newly created daemon process has a different context than expected. It won't be possible ...
- 设计模式之适配器模式(php实现)
/* github地址:https://github.com/ZQCard/design_pattern * 适配器模式:将一个类的接口转换成客户希望的另外一个接口. * 适配器模式使得原本由于接口不 ...