iOS有三种多线程编程的技术

(一)NSThread 
(二)Cocoa NSOperation
(三)GCD(全称:Grand Central Dispatch)
这三种编程方式从上到下,抽象度层次是从低到高的,抽象度越高的使用越简单,也是Apple最推荐使用的。

GCD

  异步调用的实现中往往采用并发机制,然而并不是所有异步都是并发机制,也有可能是其他机制,比如一些依靠中断进行的操作。

GCD :以优化的应用程序支持多核心处理器和其他的对称多处理系统的系统。这建立在任务并行执行的线程池模式的基础上的。它首次发布在Mac OS X 10.6 ,iOS 4及以上也可用。
 
GCD的工作原理是:让程序平行排队的特定任务,根据可用的处理资源,安排他们在任何可用的处理器核心上执行任务。
一个任务可以是一个函数(function)或者是一个block。 GCD的底层依然是用线程实现,不过这样可以让程序员不用关注实现的细节。
 
GCD中的FIFO队列称为dispatch queue

1.GCD的一个重要概念是队列(dispatch queue),它的核心理念:将长期运行的任务拆分成多个工作单元,并将这些单元添加到dispath queue中,系统会为我们管理这些dispath queue,为我们在多个线程上执行工作单元,我们不需要直接启动和管理后台线程。

2.系统提供了许多预定义的dispath queue,包括可以保证始终在主线程上执行工作的dispath queue。也可以创建自己的dispath queue,而且可以创建任意多个。GCD的dispath queue严格遵循FIFO(先进先出)原则,添加到dispath queue的工作单元将始终按照加入dispath queue的顺序启动。

3.dispatch queue按先进先出的顺序,串行或并发地执行任务

1> serial dispatch queue一次只能执行一个任务, 当前任务完成才开始出列并启动下一个任务

2> concurrent dispatch queue则尽可能多地启动任务并发执行

 

三种队列及管理:

串行队 Serial

又称为private dispatch queues,同时只执行一个任务。Serial queue通常用于同步访问特定的资源或数据。当你创建多个Serial queue时,虽然它们各自是同步执行的,但Serial queue与Serial queue之间是并发执行的。
1> 串行queue每次只能执行一个任务。你可以使用串行queue来替代锁,保护共享资源 或可变的数据结构。和锁不一样的是,串行queue确保任务按可预测的顺序执行。而且只要你异步地提交任务到串行queue,就永远不会产生死锁

2> 你必须显式地创建和管理所有你使用的串行queue,应用可以创建任意数量的串行queue,但不要为了同时执行更多任务而创建更多的串行queue。如果你需要并发地执行大量任务,应该把任务提交到全局并发queue

3> 利用dispatch_queue_create函数创建串行queue,两个参数分别是queue名和一组queue属性 
 

运行时获得公共队 Concurrent

又称为global dispatch queue,系统给每一个应用程序提供了三个这三个并发调度队列是全局的,它们只有优先级的不同。可以并发地执行多个任务,但是执行完成的顺序是随机的。

GCD提供了函数让应用访问几个公共dispatch queue:

1> 使用dispatch_get_current_queue函数作为调试用途,或者测试当前queue的标识。在block对象中调用这个函数会返回block提交到的queue(这个时候queue应该正在执行中)。在block对象之外调用这个函数会返回应用的默认并发queue。

2> 使用dispath_get_global_queue可以获取去得到concurrent dispatch queues队列,如下:

  let globalQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

  第一个参数用于指定优先级,分别使用DISPATCH_QUEUE_PRIORITY_HIGH和DISPATCH_QUEUE_PRIORITY_LOW两个常量来获取高和低优先级的两个queue;第二个参数目前未使用到,默认0即可

此外还有DISPATCH_QUEUE_PRIORITY_BACKGROUND

Main dispatch queue

它是全局可用的serial queue,它是在应用程序主线程上执行任务的。
1> 使用dispatch_get_main_queue函数获得应用主线程关联的串行dispatch queue,添加到这个queue的任务由主线程串行化执行
// 异步下载图片
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURL *url = [NSURL URLWithString:@"http://car0.autoimg.cn/upload/spec/9579/u_20120110174805627264.jpg"];
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]]; // 回到主线程显示图片
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = image;
});
});

创建队列:

let queue = dispatch_queue_create("gcdtest.rongfzh.yc", nil)               //串行

let queue = dispatch_queue_create("gcdtest.rongfzh.yc", DISPATCH_QUEUE_SERIAL)    //串行
let queue = dispatch_queue_create("gcdtest.rongfzh.yc", DISPATCH_QUEUE_CONCURRENT) //并行

添加任务入队

要执行一个任务,你需要将它添加到一个适当的dispatch queue,你可以单个或按组来添加,也可以同步或异步地执行一个任务,也。一旦进入到queue,queue会负责尽快地执行你的任务。一般可以用一个block来封装任务内容。

1.添加单个任务到queue

1> 异步添加任务

你可以异步或同步地添加一个任务到Queue,尽可能地使用dispatch_async或dispatch_async_f函数异步地调度任务。因为添加任务到Queue中时,无法确定这些代码什么时候能够执行(GCD会自动根据任务在多核处理器上分配资源,优化程序)。因此异步地添加block或函数,可以让你立即调度这些代码的执行,然后调用线程可以继续去做其它事情。特别是应用主线程一定要异步地 dispatch 任务,这样才能及时地响应用户事件

        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
// 耗时的操作
dispatch_async(dispatch_get_main_queue(), {
// 更新界面
})
})
 

2> 同步添加任务

少数时候你可能希望同步地调度任务,以避免竞争条件或其它同步错误。 使用dispatch_sync和dispatch_sync_f函数同步地添加任务到Queue,这两个函数会阻塞当前调用线程,直到相应任务完成执行。注意:绝对不要在任务中调用 dispatch_sync或dispatch_sync_f函数,并同步调度新任务到当前正在执行的 queue。对于串行queue这一点特别重要,因为这样做肯定会导致死锁;而并发queue也应该避免这样做。

        // 调用前,查看下当前线程
NSLog("当前调用线程:%@", NSThread.currentThread())
// 创建一个串行queue
let queue = dispatch_queue_create("cn.itcast.queue", nil)
dispatch_async(queue, {
NSLog("开启了一个异步任务,当前线程:%@", NSThread.currentThread())
}
) dispatch_sync(queue, {
NSLog("开启了一个同步任务,当前线程:%@", NSThread.currentThread())
}
)

打印结果:

2015-05-09 00:49:27.539 ImageLoaderExample[2122:81150] 当前调用线程:<NSThread: 0x7fcbdad27890>{number = 1, name = main}

2015-05-09 00:49:27.541 ImageLoaderExample[2122:81222] 开启了一个异步任务,当前线程:<NSThread: 0x7fcbdae66a30>{number = 2, name = (null)}

2015-05-09 00:49:27.541 ImageLoaderExample[2122:81150] 开启了一个同步任务,当前线程:<NSThread: 0x7fcbdad27890>{number = 1, name = main}

暂停和继续queue

我们可以使用dispatch_suspend函数暂停一个queue以阻止它执行block对象;使用dispatch_resume函数继续dispatch queue。调用dispatch_suspend会增加queue的引用计数,调用dispatch_resume则减少queue的引用计数。当引用计数大于0时,queue就保持挂起状态。因此你必须对应地调用suspend和resume函数。挂起和继续是异步的,而且只在执行block之间(比如在执行一个新的block之前或之后)生效。挂起一个queue不会导致正在执行的block停止。

dispatch_group_async的使用

  dispatch_group_async可以实现监听一组任务是否完成,完成后得到通知执行其他的操作。这个方法很有用,比如你执行三个下载任务,当三个任务都下载完成后你才通知界面说完成的了。下面是一段例子代码:

        let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
let group = dispatch_group_create()
dispatch_group_async(group, queue, {
NSThread.sleepForTimeInterval(1)
NSLog("group1");
})
dispatch_group_async(group, queue, {
NSThread.sleepForTimeInterval(2)
NSLog("group2");
})
dispatch_group_async(group, queue, {
NSThread.sleepForTimeInterval(3)
NSLog("group3");
})
dispatch_group_async(group, queue, {
NSThread.sleepForTimeInterval(3)
NSLog("group34");
})
dispatch_group_notify(group, dispatch_get_main_queue(), {
NSLog("updateUi");
});
dispatch_group_async是异步的方法,运行后可以看到打印结果:

2015-05-08 23:23:40.344 ImageLoaderExample[1504:48633] group1

2015-05-08 23:23:41.340 ImageLoaderExample[1504:48631] group2

2015-05-08 23:23:42.340 ImageLoaderExample[1504:48638] group34

2015-05-08 23:23:42.340 ImageLoaderExample[1504:48634] group3

2015-05-08 23:23:42.341 ImageLoaderExample[1504:48547] updateUi

每个一秒打印一个,当第三个任务执行后,upadteUi被打印。

// 根据url获取UIImage
- (UIImage *)imageWithURLString:(NSString *)urlString {
NSURL *url = [NSURL URLWithString:urlString];
NSData *data = [NSData dataWithContentsOfURL:url];
// 这里并没有自动释放UIImage对象
return [[UIImage alloc] initWithData:data];
} - (void)downloadImages {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); // 异步下载图片
dispatch_async(queue, ^{
// 创建一个组
dispatch_group_t group = dispatch_group_create(); __block UIImage *image1 = nil;
__block UIImage *image2 = nil; // 关联一个任务到group
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 下载第一张图片
NSString *url1 = @"http://car0.autoimg.cn/upload/spec/9579/u_20120110174805627264.jpg";
image1 = [self imageWithURLString:url1];
}); // 关联一个任务到group
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 下载第二张图片
NSString *url2 = @"http://hiphotos.baidu.com/lvpics/pic/item/3a86813d1fa41768bba16746.jpg";
image2 = [self imageWithURLString:url2];
}); // 等待组中的任务执行完毕,回到主线程执行block回调
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
self.imageView1.image = image1;
self.imageView2.image = image2; // 在这里释放图片资源
[image1 release];
[image2 release];
}); // 释放group
dispatch_release(group);
});
}

dispatch_barrier_async的使用

在前面的任务执行结束后它才执行,而且它后面的任务等它执行完成之后才会执行

        NSLog("begin");
let queue = dispatch_queue_create("gcdtest.rongfzh.yc", DISPATCH_QUEUE_CONCURRENT)
dispatch_async(queue, {
NSThread.sleepForTimeInterval(2)
NSLog("dispatch_async1");
}
);
dispatch_async(queue, {
NSThread.sleepForTimeInterval(4)
NSLog("dispatch_async2");
}
);
dispatch_barrier_async(queue, {
NSLog("dispatch_barrier_async");
NSThread.sleepForTimeInterval(4)
}
);
dispatch_async(queue, {
NSThread.sleepForTimeInterval(1)
NSLog("dispatch_async3");
}
);
dispatch_async(queue, {
NSThread.sleepForTimeInterval(1)
NSLog("dispatch_async4");
}
);
打印结果:

2015-05-08 23:39:11.729 ImageLoaderExample[1635:55195] begin

2015-05-08 23:39:13.731 ImageLoaderExample[1635:55268] dispatch_async1

2015-05-08 23:39:15.730 ImageLoaderExample[1635:55267] dispatch_async2

2015-05-08 23:39:15.731 ImageLoaderExample[1635:55267] dispatch_barrier_async

2015-05-08 23:39:20.742 ImageLoaderExample[1635:55268] dispatch_async4

2015-05-08 23:39:20.742 ImageLoaderExample[1635:55267] dispatch_async3

dispatch_after 用于延迟执行一些代码

  ,dispatch_block_t ,可用于取消dispatch_after 的延时任务

纳秒  NSEC_PER_SEC  = 109

 //let delayInSeconds:Int64 = 1000,000,000 * 2  // Int64(2 * NSEC_PER_SEC)
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(2 * NSEC_PER_SEC))
dispatch_after(time, dispatch_get_main_queue()){
//...
}

dispatch_once 只执行一次,用于一些单例。

+ (instancetype)sharedInstance {

  static id sharedInstance;

  static dispatch_once_t onceToken;

  dispatch_once(&onceToken, ^{

    sharedInstance = [self new];

  });

  return sharedInstance;

} 

dispatch_apply 并发地执行循环迭代

如果你使用循环执行固定次数的迭代, 并发dispatch queue可能会提高性能。可以指定串行抑或并行

和普通for循环一样,dispatch_apply和dispatch_apply_f函数也是在所有迭代完成之后才会返回,因此这两个函数会阻塞当前线程。如果你传递的参数是串行queue,而且正是执行当前代码的queue,就会产生死锁。

let queue = dispatch_queue_create("gcdtest.rongfzh.yc", DISPATCH_QUEUE_CONCURRENT)
dispatch_apply(30, queue, {print("|*\($0)*|")})

打印结果:

|*0*||*4*||*5*|||||****6312****||||*|7||***|8|9**|*1|*|01|*1|*|**1|||12**3*15*||*1|*16|4*|**|1|||**|711**98|2**|0||**||2|*|1*2**2223|*4*|*||||**|2|2*56*2**2|7|8*||**|29*|

let queue = dispatch_queue_create("gcdtest.rongfzh.yc", DISPATCH_QUEUE_SERIAL)
dispatch_apply(30, queue, {print("|*\($0)*|")})

打印结果:

|*0*||*1*||*2*||*3*||*4*||*5*||*6*||*7*||*8*||*9*||*10*||*11*||*12*||*13*||*14*||*15*||*16*||*17*||*18*||*19*||*20*||*21*||*22*||*23*||*24*||*25*||*26*||*27*||*28*||*29*|

更细粒度的排他控制:dispatch_semaphore

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// Create dispatch_semaphore
// semaphore value初始化为1
/ 保证可访问NSMutableArray类对象的线程同时只有一个
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
NSMutableArray *array = [[NSMutableArray alloc] init];
for (int i = 0; i < 100000; i++) {
dispatch_async(queue, ^{
// Waiting for dispatch semaphore, 直到semaphore值达到大于等于1
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
// 由于Dispatch semaphore的计数值达到大于等于1
// 所以将Dispatch semaphore的计数值减1
// dispatch_semaphore_wait函数执行返回
// 即执行到此时的Dispatch semaphore计数值恒为0
// 由于可访问NSMutableArray类对象的线程只有一个
// 因此可安全进行更新
[array addObject:[NSNumber numberWithInt:i]];
// 排他控制处理结束
// 所以通过dispatch_semaphore_signal函数
// 将Dispatch semaphore的计数值加1
// 如果有通过dispatch_semaphore_wait函数等待Dispatch semaphore的
// 计数值增加的线程,由最先等待的线程执行
dispatch_semaphore_signal(semaphore);
});
}
26 dispatch_release(semaphore);

Dispatch I/O:并发读取文件数据,高效率读取文件:

// 并发读取文件原理
dispatch_async(queue, ^{/* 读取0-8191字节*/});
dispatch_async(queue, ^{/* 读取8192-16383字节*/});
dispatch_async(queue, ^{/* 读取163784-24575字节*/});
dispatch_async(queue, ^{/* 读取24576-32767字节*/});
dispatch_async(queue, ^{/* 读取32768-40959字节*/});
dispatch_async(queue, ^{/* 读取40960-49151字节*/});
dispatch_async(queue, ^{/* 读取49152-57343字节*/});
dispatch_async(queue, ^{/* 读取57344-65535字节*/}); // 实例代码如下
dispatch_queue_t pipe_q = dispatch_queue_create("PipQ", NULL);
dispatch_io_t pipe_channel = dispatch_io_create(DISPATCH_IO_STREAM, fd, pip_q, &(int err){
close(fd);
}); *out_fd = dfpair[1];
// 设定函数一次读取的大小(分割大小)
dispatch_io_set_low_water(pipe_channel, SIZE_MAX);
dispatch_io_read(pipe_channel, 0, SIZE_MAX, pipe_q, ^{
if (0 == err) {
size_t len = dispatch_data_get_size(pipedata);
if (len > 0) {
const char *bytes = NULL;
char *encoded;
dispatch_data_t md = dispatch_data_create_map(pipedata, (const void **)&bytes, &len);
encoded = asl_core_encode_buffer(bytes, len);
asl_set((aslmsg)merged_msg, ASL_KEY_AUX_DATA, encoded);
free(encoded);
_asl_send_message(NULL, merged_msg, -1, NULL);
_asl_msg_release(merged_msg);
dispatch_release(md);
}
}
if (done) {
dispatch_semaphore_signal(sem);
dispatch_release(pipe_channel);
dispatch_release(pipe_q);
}
});

  

  

 

IOS并发编程GCD的更多相关文章

  1. iOS并发编程笔记【转】

    线程 使用Instruments的CPU strategy view查看代码如何在多核CPU中执行.创建线程可以使用POSIX 线程API,或者NSThread(封装POSIX 线程API).下面是并 ...

  2. iOS 并发编程指南

    iOS Concurrency Programming Guide iOS 和 Mac OS 传统的并发编程模型是线程,不过线程模型伸缩性不强,而且编写正确的线程代码也不容易.Mac OS 和 iOS ...

  3. IOS中的多核并发编程GCD

    Grand Central Dispatch (GCD)是Apple开发的一个多核编程的解决方法. dispatch queue分成以下三种: 1)运行在主线程的Main queue,通过dispat ...

  4. iOS并发编程指南之同步

    1.gcd fmdb使用了gcd,它是通过 建立系列化的G-C-D队列 从多线程同时调用调用方法,GCD也会按它接收的块的顺序来执行. fmdb使用的是dispatch_sync,多线程调用a ser ...

  5. iOS: 并发编程的几个知识点

    iOS 多线程问题 查阅的大部分资料都是英文的,整理完毕之后,想翻译成中文,却发现很多名字翻译成中文很难表述清楚. 所以直接把整理好的资料发出来,大家就当顺便学习学习英语. 1. Thread Saf ...

  6. iOS 并发编程之 Operation Queues

    现如今移动设备也早已经进入了多核心 CPU 时代,并且随着时间的推移,CPU 的核心数只会增加不会减少.而作为软件开发者,我们需要做的就是尽可能地提高应用的并发性,来充分利用这些多核心 CPU 的性能 ...

  7. iOS 并行编程:NSOperation Queues

    1 简介 1.1 功能        Operation Queue也是IOS的一种并行编程技术,类似Dispatch Queue可以帮助用户管理多线程.但是Operation Queue将任务封装在 ...

  8. iOS多线程编程Part 3/3 - GCD

    前两部分介绍了NSThread.NSRunLoop和NSOperation,本文聊聊2011年WWDC时推出的神器GCD.GCD: Grand Central Dispatch,是一组用于实现并发编程 ...

  9. iOS -NSOperation并发编程

    http://www.cocoachina.com/game/20151201/14517.html http://blog.csdn.net/qinlicang/article/details/42 ...

随机推荐

  1. Thread对象的yield(),wait(),notify(),notifyall()

    Thread类中的主要方法: join()方法:让一个线程强制运行,线程强制运行期间,其他线程无法运行,必须等到此线程完成之后才可以继续执行. setDaemon():设置线程为后台线程,这样即使Ja ...

  2. Unity4.3 bug GetChild顺序错乱

    历史原因,目前有个项目还在使用unity4.3版本,比较过不同Unity版本,发现unity4.3的 transform.GetChild 获取的child顺序并不是想要的. 测试代码 using U ...

  3. C# 匿名函数 详解

    匿名函数的定义和用途 匿名函数是一个“内联”语句或表达式,可在需要委托类型的任何地方使用. 可以使用匿名函数来初始化命名委托[无需取名字的委托],或传递命名委托(而不是命名委托类型,传递一个方法块,而 ...

  4. 给VIM安装插件。让ubuntu的vim强大起来

    简易安装方法: 打开终端,执行下面的命令就自动安装好了: wget https://raw.github.com/ma6174/vim/master/setup.sh -O ma6174_vim_se ...

  5. [No000009]学习重要还是经营人脉重要?

    大说数人的朋友圈都是这样的:雪中送炭的寥寥无几,锦上添花的大多数人连你自己都不认识,碰到倒霉的时候还能遇到落进下石的.人脉是很重要,可相对自身的学习来说,就没有想象中那么重要了. 有一次在北大讲座,遇 ...

  6. windows下使用 linux命令好办法

    1. 安装下载 CygwinPortable一键安装包.7z 2. 把安装路径下/  [D:\cygwinportable\CygwinPortable\App\Cygwin\bin] 加到 Path ...

  7. 当一名黑客获得一份WebShell后,会做什么

    当你获得一份webshell后,你会干嘛? 我曾获得N多webshell.什么discuz.dedecms.phpwind.phpweb.aspcms等等,甚至还包括N多自己研发的线上平台. 可是,问 ...

  8. BZOJ 4241 历史研究

    Description IOI国历史研究的第一人——JOI教授,最近获得了一份被认为是古代IOI国的住民写下的日记.JOI教授为了通过这份日记来研究古代IOI国的生活,开始着手调查日记中记载的事件. ...

  9. ZooKeeper 笔记(4) 实战应用之【消除单点故障】

    关键节点的单点故障(Single Point of Failure)在大型的架构中,往往是致命的.比如:SOA架构中,服务注册中心(Server Register)统一调度所有服务,如果这个节点挂了, ...

  10. XStream、JAXB 日期(Date)、数字(Number)格式化输出xml

    XStream.Jaxb是java中用于对象xml序列化/反序列化 的经典开源项目,利用它们将对象转换成xml时,经常会遇到日期(Date).数字按指定格式输出的需求,下面是使用示例: 一.日期字段格 ...