在上一篇中,我们主要讲了Dispatch Queue相关的内容。这篇主要讲一下一些和实际相关的使用实例,Dispatch Groups和Dispatch Semaphore。

dispatch_after

在我们开发过程中经常会用到在多少秒后执行某个方法,通常我们会用这个- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay函数。不过现在我们可以使用一个新的方法。

dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC);
dispatch_after(delayTime, dispatch_get_main_queue(), ^{
//do your task
});

这样我们就定义了一个延迟2秒后执行的任务。不过在这里有一点需要说明的是,无论你用的是- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay还是dispatch_after这个方法。并不是说在你指定的延迟后立即运行,这些方法都是基于单线程的,它只是将你延迟的操作加入到队列里面去。由于队列里面都是FIFO,所以必须在你这个任务之前的操作完成后才会执行你的方法。这个延迟只是大概的延迟。如果你在主线程里面调用这个方法,如果你主线程现在正在处理一个非常耗时的任务,那么你这个延迟可能就会偏差很大。这个时候你可以再开个线程,在里面执行你的延迟操作。

//放到全局默认的线程里面,这样就不必等待当前调用线程执行完后再执行你的方法
dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC);
dispatch_after(delayTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//do your task
});

dispatch_once

这个想必大家都非常的熟悉,这个在单例初始化的时候是苹果官方推荐的方法。这个函数可以保证在应用程序中只执行指定的任务一次。即使在多线程的环境下执行,也可以保证百分之百的安全。

    static id instance;
static dispatch_once_t predicate; dispatch_once(&predicate, ^{
//your init
}); return instance;
}

这里面的predicate必须是全局或者静态对象。在多线程下同时访问时,这个方法将被线程同步等待,直到指定的block执行完成。

dispatch_apply

这个方法是执行循环次数固定的迭代,如果在并发的queue里面可以提高性能。比如一个固定次数的for循环

for (int i = 0; i < 1000; i ++) {
NSLog(@"---%d---", i);
}

如果只是在一个线程里面或者在一个串行的队列中是一样的,一个个执行。

现在我们用dispatch_apply来写这个循环:

dispatch_apply([array count], defaultQueue, ^(size_t i) {
NSLog(@"----%@---", array[i]);
});
NSLog(@"end");

这个方法执行后,它将像这个并发队列中不断的提交执行的block。这个i是从0开始的,最后一个是[array count] - 1

使用这个方法有几个注意点:

  1. 这个方法调用的时候会阻塞当前的线程,也就是上面的循环全部执行完毕后,才会输出end
  2. 在你使用这个任务进行操作的时候,你应该确保你要执行的各个任务是独立的,而且执行顺序也是无关紧要的。
  3. 在你使用这个方法的时候,你还是要权衡下整体的性能的,如果你执行的任务时间比线程切换的时间还短。那就得不偿失了。

dispatch_group

在实际开发中,我们可能需要在一组操作全部完成后,才做其他操作。比如上传一组图片,或者下载多个文件。希望在全部完成时给用户一个提示。如果这些操作在串行化的队列中执行的话,那么你可以很明确的知道,当最后一个任务执行完成后,就全部完成了。这样的操作也并木有发挥多线程的优势。我们可以在并发的队列中进行这些操作,但是这个时候我们就不知道哪个是最后一个完成的了。这个时候我们可以借助dispatch_group:

    dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, defaultQueue, ^{
//task1
NSLog(@"1");
});
dispatch_group_async(group, defaultQueue, ^{
//task2
NSLog(@"2");
});
dispatch_group_async(group, defaultQueue, ^{
//task3
NSLog(@"3");
});
dispatch_group_async(group, defaultQueue, ^{
//task4
NSLog(@"4");
});
dispatch_group_async(group, defaultQueue, ^{
//task5
NSLog(@"5");
}); dispatch_group_notify(group, queue, ^{
NSLog(@"finish");
});

我们首先创建一个group然后往里面加入我们要执行的操作,在dispatch_group_notify这个函数里面添加全部完成的操作。上面代码执行的时候,输出的1,2,3,4,5的顺序是不一定的,但是输出的finish一定是在1,2,3,4,5之后。

对于添加到group的操作还有另外一个方法:

    dispatch_group_enter(group);
dispatch_group_enter(group); dispatch_async(defaultQueue, ^{
NSLog(@"1");
dispatch_group_leave(group);
}); dispatch_async(defaultQueue, ^{
NSLog(@"2");
dispatch_group_leave(group);
}); dispatch_group_notify(group, queue, ^{
NSLog(@"finish");
});

我们可以用dispatch_group_enter来表示添加任务,dispatch_group_leave来表示有个任务已经完成了。用这个方法一定要注意必须成双成对。

线程同步

在多线程中一个比较重要的东西就是线程同步的问题。如果多个线程只是对某个资源只是读的过程,那么就不存在这个问题了。如果某个线程对这个资源需要进行写的操作,那这个时候就会出现数据不一致的问题了。

使用dispatch_barrier_async

    __block NSString *strTest = @"test";

    dispatch_async(defaultQueue, ^{
if ([strTest isEqualToString:@"test"]) {
NSLog(@"--%@--1-", strTest);
[NSThread sleepForTimeInterval:1];
if ([strTest isEqualToString:@"test"]) {
[NSThread sleepForTimeInterval:1];
NSLog(@"--%@--2-", strTest);
} else {
NSLog(@"====changed===");
}
}
});
dispatch_async(defaultQueue, ^{
NSLog(@"--%@--3-", strTest);
});
dispatch_async(defaultQueue, ^{
strTest = @"modify";
NSLog(@"--%@--4-", strTest);
});

看看这个模拟的场景,我们让各个线程去访问这个变量,其中有个操作是要修改这个变量。我们把第一个操作先判断有木有改变,然后故意延迟一下,这个时候我们看下输出结果:

2015-01-03 15:42:21.351 测试[1652:60015] --test--3-
2015-01-03 15:42:21.351 测试[1652:60013] --modify--4-
2015-01-03 15:42:21.351 测试[1652:60014] --test--1-
2015-01-03 15:42:22.355 测试[1652:60014] ====changed===

我们可以看到,再次判断的时候,已经被修改了,如果我们在实际的业务中这样去判断某些关键性的变量,可能就会出现严重的问题。下面看看我们如何使用dispatch_barrier_async来进行同步:

 //并发队列
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.gcd.concurrentQueue", DISPATCH_QUEUE_CONCURRENT); __block NSString *strTest = @"test"; dispatch_async(concurrentQueue, ^{
if ([strTest isEqualToString:@"test"]) {
NSLog(@"--%@--1-", strTest);
[NSThread sleepForTimeInterval:1];
if ([strTest isEqualToString:@"test"]) {
[NSThread sleepForTimeInterval:1];
NSLog(@"--%@--2-", strTest);
} else {
NSLog(@"====changed===");
}
}
});
dispatch_async(concurrentQueue, ^{
NSLog(@"--%@--3-", strTest);
});
dispatch_barrier_async(concurrentQueue, ^{
strTest = @"modify";
NSLog(@"--%@--4-", strTest);
});
dispatch_async(concurrentQueue, ^{
NSLog(@"--%@--5-", strTest);
});

现在看下输出结果:

2015-01-03 16:00:27.552 测试[1786:65947] --test--1-
2015-01-03 16:00:27.552 测试[1786:65965] --test--3-
2015-01-03 16:00:29.553 测试[1786:65947] --test--2-
2015-01-03 16:00:29.553 测试[1786:65947] --modify--4-
2015-01-03 16:00:29.553 测试[1786:65947] --modify--5-

现在我们可以发现操作4用dispatch_barrier_async加入操作后,前面的操作3之前都操作完成之前这个strTest都没有变。而后面的操作都是改变后的值。这样我们的数据冲突的问题就解决了。

现在说明下这个函数干的事情,当这个函数加入到队列后,里面block并不是立即执行的,它会先等待之前正在执行的block全部完成后,才执行,并且在它之后加入到队列中的block也在它操作结束后才能恢复之前的并发执行。我们可以把这个函数理解为一条分割线,之前的操作,之后加入的操作。还有一个点要说明的是这个queue必须是用dispatch_queue_create创建出来的才行。

使用Dispatch Semaphore

dispatch_semaphore_t 类似信号量,可以用来控制访问某一资源访问数量。

使用过程:

  1. 先创建一个Dispatch Semaphore对象,用整数值表示资源的可用数量
  2. 在每个任务中,调用dispatch_semaphore_wait来等待
  3. 获得资源就可以进行操作
  4. 操作完后调用dispatch_semaphore_signal来释放资源
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
__block NSString *strTest = @"test"; dispatch_async(concurrentQueue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
if ([strTest isEqualToString:@"test"]) {
NSLog(@"--%@--1-", strTest);
[NSThread sleepForTimeInterval:1];
if ([strTest isEqualToString:@"test"]) {
[NSThread sleepForTimeInterval:1];
NSLog(@"--%@--2-", strTest);
} else {
NSLog(@"====changed===");
}
}
dispatch_semaphore_signal(semaphore);
});
dispatch_async(concurrentQueue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"--%@--3-", strTest);
dispatch_semaphore_signal(semaphore);
});
dispatch_async(concurrentQueue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
strTest = @"modify";
NSLog(@"--%@--4-", strTest);
dispatch_semaphore_signal(semaphore);
});
dispatch_async(concurrentQueue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"--%@--5-", strTest);
dispatch_semaphore_signal(semaphore);
});

这样我们一样可以保证,线程的数据安全。

iOS多线程GCD简介(二)的更多相关文章

  1. iOS多线程GCD简介(一)

    之前讲过多线程之NSOperation,今天来讲讲代码更加简洁和高效的GCD.下面说的内容都是基于iOS6以后和ARC下. Grand Central Dispatch (GCD)简介 Grand C ...

  2. iOS 多线程GCD简介

    一.简介 1.1 GCD (Grand Central Dispatch )是Apple开发的一个多核编程的解决方法. Grand 含义是“伟大的.宏大的”,Central含义“中央的”,Dispat ...

  3. iOS多线程 GCD

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

  4. iOS 多线程GCD的基本使用

    <iOS多线程简介>中提到:GCD中有2个核心概念:1.任务(执行什么操作)2.队列(用来存放任务) 那么多线程GCD的基本使用有哪些呢? 可以分以下多种情况: 1.异步函数 + 并发队列 ...

  5. iOS多线程——GCD与NSOperation总结

    很长时间以来,我个人(可能还有很多同学),对多线程编程都存在一些误解.一个很明显的表现是,很多人有这样的看法: 新开一个线程,能提高速度,避免阻塞主线程 毕竟多线程嘛,几个线程一起跑任务,速度快,还不 ...

  6. iOS多线程GCD的使用

    1. GCD 简介 Grand Central Dispatch(GCD)是异步执行任务的技术之一.一般将应用程序中记述的线程管理用的代码在系统级中实现.开发者只需要定义想执行的任务并追加到适当的Di ...

  7. iOS 多线程 GCD part3:API

    https://www.jianshu.com/p/072111f5889d 2017.03.05 22:54* 字数 1667 阅读 88评论 0喜欢 1 0. 预备知识 GCD对时间的描述有些新奇 ...

  8. iOS多线程GCD的简单使用

    在iOS开发中,苹果提供了三种多线程技术,分别是: (1)NSThread (2)NSOperation (3)GCD 简单介绍一下GCD的使用. GCD全称 Grand Central Dispat ...

  9. ios多线程-GCD基本用法

    ios中多线程有三种,NSTread, NSOperation,GCD 这篇就讲讲GCD的基本用法 平时比较多使用和看到的是: dispatch_async(dispatch_get_global_q ...

随机推荐

  1. C# RSA 无 长度限制 加密解密 示例

    RSA 是一种非对称加密算法.由于算法特性,加密和解密过程用不同密钥,即公钥和私钥,而被广泛应用于数字证书的安全管理. 在具体应用中,公钥用加密而私钥用于解密,或 私钥用于数字签名而公钥用于签名验证. ...

  2. parquet 简介(转)

    原文 Parquet 列式存储格式 面向分析型业务的列式存储格式 由 Twitter 和 Cloudera 合作开发,2015 年 5 月从 Apache 的孵化器里毕业成为 Apache 顶级项目 ...

  3. JS的base64编码解码

    Unicode问题解法 有个小坑是它只支持ASCII. 如果你调用btoa("中文")会报错: Uncaught DOMException: Failed to execute ' ...

  4. 容器docker快速入门

    一.概述 什么是docker docker是一个应用容器引擎,通俗的讲,docker和我们的vm虚拟机有很多相似的地方,当然也有很多不同的地方 Docker理念是将应用及依赖包打包到一个可移植的容器中 ...

  5. Python 处理异常栈模块——traceback 模块

    异常捕捉 通常我们在项目中,针对异常的捕捉会使用 try + except,基本形式如下: try: # 主代码 except IndexError as e: # 索引异常时执行这里 logger. ...

  6. Prometheus监控实战day1-监控简介

    福利 Prometheus监控实战PDF电子书下载 链接:https://pan.baidu.com/s/1QH4Kvha5g70OhYQdp4YsfQ 提取码:oou5 若你喜欢该资料,请购买该资料 ...

  7. hive 引入第三方包(不重启)

    Jar放入${HIVE_HOME}/auxlib目录 在${HIVE_HOME}中创建文件夹auxlib,然后将自定义jar文件放入该文件夹中. 此方法添加不需要重启Hive.而且比较便捷. 连接方式 ...

  8. 服务器BMC资料整理

    1. 现在服务器都有BMC管理了,可以直接连上服务器进行处理. bios里面进行简单设置就可以了, 连接上IPMI的口进行管理. 2. 可以使用 远程控制安装操作系统. 安装系统时 比较清楚的能够看到 ...

  9. WAV文件读取

    WAV是一种以RIFF为基础的无压缩音频编码格式,该格式以Header.Format Chunk及Data Chunk三部分构成. 本文简要解析了各部分的构成要素,概述了如何使用C++对文件头进行解析 ...

  10. Jupyter notebook 安装

    一.建议从官网下载最新版anaconda https://www.anaconda.com/ 进入网址找到下载位置,并找到对应的版本,下载python3.7,根据电脑系统自行选择32/64位进行下载, ...