为什么要用GCD-Swift2.x

为什么要用GCD-Swift2.x

当今世界,多核已然普及。但是APP却不见得很好的跟上了这个趋势。APP
想要利用好多核就必须可以保证任务能有效的分配。并行执行可以让APP同时执行很多
的任务。这个其实很难,但是有了GCD一切都变得简单了很多。

你并不是一定要写一个大并发的APP才需要用GCD。使用GCD可以让你的APP更快的
响应用户的操作,不用要等到你的UI或者服务等到执行完成。一般来说你会把各种任务
都分配给其他核心去执行,而你的主线程(UI线程)可以随时处理用户的操作。
GCD可以让这些变得简单。

线程

传统的多任务分发方式是使用线程。在一个多核的设备上,每一个新的线程都可以被分配在一个CPU核心
上,与其他的线程并行执行。

单核心的CPU麻烦一些,系统会不停地在几个线程之间切换以保证每个线程都有机会执行。这样做的效果
是看起来像是并行执行的,但是其实多个线程的不同任务之间是顺序执行的。

但是使用线程也会遇到一个很大的问题。数据在线程之间的正确传递就是一个很大的难题了。线程之间的
同步和互斥又会变得很诡异难以调试。而且,为了保证APP的高效快速运行,开辟多少线程也是一个需要思考的
问题。因为,创建和销毁线程也是有很大的资源消耗的。于是很多的系统都提供了一个叫做线程池
的概念来解决这个问题。所有的线程创建后都放在这个池子里统一管理。

同步

当你有多个线程在执行的时候,你一般都会遇到一个问题Race Condition,实际的运算结果
取决于那个线程先获得共享数据。一个经典的例子就是:银行账户问题。

class BankAccount {
    var balance: Double?

    //...
}

// 创建一个账号,给这个账号存100块
var account = BankAccount()
account.balance = 100

// 第一个线程:取10块
func withdraw() {
    let balance = account.balance
    account.balance = balance! - 10
}

// 第二个线程:增加10%的利息
func accrue() {
    let balance = account.balance
    account.balance = balance! * 1.10
}

// 那么最后:account.balance = ?

最后的结果取决于这两个线程哪一个先执行。在并行的条件下执行的先后顺序是不定的。但是执行顺序不同
最后的balance值就是不同的。

上面的代码会有多少个可能的不同结果?balance都会是什么值?
Race Condition即使在单核设备下也会发生。

对于这些问题,传统的解决方法如下:
* 信号量(semaphore)- 用来控制一组有限资源的消费。线程等待,知道资源可用。
* 互斥(Mutex)- 一次只允许一个线程执行。当一个线程持有mutex的时候其他线程等待。
* 条件变量(Condvar)- 线程等待直到某些条件为真。

队列

GCD把线程的创建、回收以及线程的同步等进一步抽象为队列(Queue)统一管理。

队列,简单而言,就是一个可以让数据按照先进先出的顺序执行。一个APP可以创建多个队列,并且多个
队列之间可以并行的处理各自的任务,每个队列内部的任务顺序执行。

队列比线程有很多的优势。第一,GCD库屏蔽了线程管理的繁琐部分。队列会在需要的时候自动创建线程
并且在不需要的时候回收。其次,GCD库会根据系统的CPU核心数创建最佳数量的线程。最后,队列只会
在需要的时候创建线程,所以资源利用会得到优化。

总之,队列给你了你线程能给的,但是又不用考虑具体线程的操作。

GCD有三种队列:
* 顺序队列(Serial)- 每次执行一个任务,按照任务加入队列的顺序。一个任务执行完成后执行下一个。
* 并发(Concurrent)- 按照任务加入队列的顺序开始,但是后面的任务不用等到前面的任务执行完成就可以开始。
* 主线程(Main)- 一个预先开启的序列线程。这个队列中包含一个NSRunLoop实例。你的APP总是在这个队列中运行。

系统提供了几种并发队列。这些队列有自己专属的QoS(服务质量种类)。这个服务质量种类是用来表示你提交的
任务的意图是什么,这样GCD可以有针对性的优化。
* QOS_CLASS_USER_INTERACTIVE 这个用户交互(user interactive)表示任务需要立即执行,以便APP
给用户一个良好的用户体验。一般用于更新UI、处理事件和小延迟的处理。在你的整个APP中,这以种类的任务应该保持
一个较低的总量。

  • QOS_CLASS_USER_INITIATED 用户初始化(user initiated)表示任务是在UI初始化的,并且可以
    异步执行。这个一般用于处理用户等待的需要理解给出运行结果,或者任务需要理解完成用户交互的情况。

  • QOS_CLASS_UTILITY 通用(utility)表示长期执行的任务,一般来说用户可以见到任务执行的比例。
    一般用来处理大的计算、I/O、网络以及不简单的数据提交等类似任务。这一种类做了电量优化。

  • QOS_CLASS_BACKGROUND 后台运行(background)表示用户并不知道任务在运行中。这个一般用于
    数据的提前加载、维护,以及其他无需用户交互和任务完成时间无明显限制的情况。

这里需要注意一点,苹果的API也会用到这些全局分发的队列。所以,在这些队列里并不是只有你的任务在执行。当然,
如前文所述你也可以创建你自己的队列。你可以选择4种全局队列,主队列,还有两种自定义队列可以选择。

Closure

队列的任务使用closure封装。

你也可以使用方法加入队列,不过这里只使用block

这里有一个closure的例子,让你对closure有一个大概的印象。swift的closure就和Objective-C的block
差不多。

func makeIncrementer() -> ((Int) -> Int) { func addOne(number: Int) -> Int {
        return 1 + number
    }

    return addOne
}

var increment = makeIncrementer()

increment(9)

上面的例子makeIncrementer返回一个closure,这个closure需要一个整型参数。

Hello dispatch world!

这里需要注意一点:Objective-C的block和Swift的closure。
Swift的closure和Objective-C的block是兼容的。所以你完全可以把Swift的closure传入Objective-C
中需要block参数的方法中。Swift的closure和方法是同一类型的,所以你甚至可以把swift的方法名传递过去。

Closure和block的上下文处理语法基本一样。唯一不同的是变量在closure中直接就是可变的。也就是说Objective-C
中的__block关键字在Swift的closure中是默认行为。

dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE,0),{ print("hello dispatch:- user interactive") })

dispatch_async(dispatch_get_main_queue(),{ print("hello dispatch:- main queue") })

调用dispatch_async(),GCD就会给队列添加一个closure任务,然后继续代码的执行完全不会等待closure
的结束。在我们的例子中,第一次dispatch_async是给QOS_CLASS_USER_INTERACTIVE的类型的全局
队列添加了一个任务。第二次是给dispatch_get_main_queue,也就是主线程添加了一个任务。

下面看一个自创队列的例子:

@IBAction func concurrentAction(sender: AnyObject) {
    let concurrentQueue = dispatch_queue_create("concurrent.test.queue",DISPATCH_QUEUE_CONCURRENT) //1
    for i in 0...1000 { 
        dispatch_async(concurrentQueue){ //2
            NSThread.sleepForTimeInterval(1) //3 
            print("print concurrent queue:- \(i)") //4
        }
    }
}

这里一步一步的介绍一下:
1. 创建一个名称为concurrent.test.queue的并发队列。
2. 做一个1001次的循环,每个循环给这个队列添加一个closure。以上的写法只是一个简写,其实是这样的:

dispatch_async(concurrentQueue,{ //2
        NSThread.sleepForTimeInterval(1) //3
        print("print concurrent queue:- \(i)") 
})
  1. 当前线程休眠一秒

Barriers

很多人看到这里就会想,并发队列在哪儿体现出来队列的概念了呢?这分明就是一个把一堆closure扔进去分开执行的或者Set(集合)
而已嘛。

目前来看是的,但是当你遇到barrier的时候对列就真的变成队列了。你可以使用dispatch_barrier_sync
dispatch_barrier_async两个方法把closure加入队列中。这个时候就很有意思了。扔进去的closure并
不会立刻执行,而是要等。等到在这个closure之前扔进队列的全部closure都执行完成之后才开始执行。然后,
在这个barrier的closure执行完成之后,在它后面扔进队列的closure才会被执行。可以把barrier的closure认为
是一个特殊的点,在这个点的从前到后都是顺序执行的。除此之外的点,还是并行执行。

我们来看看具体的例子:

let concurrentQueue = dispatch_queue_create("concurrent.test.queue",DISPATCH_QUEUE_CONCURRENT)

var count: Int = 0

for _ in 0...100 {
    dispatch_async(concurrentQueue){
        NSThread.sleepForTimeInterval(1)
        print("print concurrent queue:- \(count++)")
    }
}

dispatch_barrier_async(concurrentQueue,{
    print("##ASYNC in barrier,concurrent queue - START")
    for _ in 1...10 {
        NSThread.sleepForTimeInterval(0.5)
    }
    print("##ASYNC in barrier,concurrent queue - END")
})

for _ in 0...100 {
    dispatch_async(concurrentQueue){
        NSThread.sleepForTimeInterval(1)
        print("print concurrent queue:- \(count++)")
    }
}

dispatch_barrier_sync(concurrentQueue,{
    print("##SYNC in barrier,concurrent queue - START")
    for _ in 1...10 {
        NSThread.sleepForTimeInterval(0.5)
    }
    print("##SYNC in barrier,concurrent queue - END")
})

for _ in 0...100 {
    dispatch_async(concurrentQueue){
        NSThread.sleepForTimeInterval(1)
        print("print concurrent queue:- \(count++)")
    }
}

注意barrier最好是用在自己创建的并发队列上。否则的话dispatch_barrier_async的效果就和dispatch_async一样,而
dispatch_barrier_sync就和dispatch_sync一样。dispatch_barrier_sync要分配的队列是当前队列
的话会造成死锁。

读写锁

先看一个新闻累的单例的例子。这个例子的最初形态中不是一个线程安全的单例:

class NewsFeed {
    static let sharedInstance = NewsFeed() //1

    private init() {} //2

    private var _news: [String] = []
    var news: [String] {
        return _news
    }

    func addNews(n: String) {
        _news.append(n)
    }
}

有这么一个新闻的类。
1. 实现一个单例。Swift的单例明显要比Objective-C简单了很多。是的,Swift的static属性自动内置了
dispatch_once。所以,这么一样就实现了Swift的单例。
2. init方法只能给类的内部调用,但是不能给外部在调用,否则就不是单例了。

用户可以调用news属性来读取全部的新闻,也可以通过调用方法addNews来添加新闻。
当时当多个线程都可以访问addNews方法的时候,那么news属性读出来的新闻列表就有很大的可能是错的。
我们现在使用barrier来确保这个功能可以正确的执行。

解决办法就是任何的线程要添加新闻,那么就必须通过barrier这一道关口。在添加新闻的时候只有一个closure执行。
其他的添加closure等待。

为了保证线程的安全,读取新闻的操作也只能在concurrentQueue上执行。但是这次我们是需要立即返回结果的
没法使用dispatch_async。这个时候使用dispatch_sync就是最好的选择了。使用dispatch_sync
可以等到方法执行完毕,并返回我们需要的结果。dispatch sync可以知道dispatch barrier的进度如何,添加了
几条新闻。也可以让closure执行完成并返回。

但是使用dispatch sync需要很小心。如前所述,如果你给dispatch sync分配的队列是当前正在运行的队列
会造成死锁。因为调用会等待这个closure结束,这个closure甚至可能都没法开始。因为这个closure需要等到
它前面运行的closure结束之后才能开始。

private var _news: [String] = []
    var news: [String] {
        var newsCopy: [String]!
        dispatch_sync(concurrentQueue){ //1
            newsCopy = self._news //2
        }
        return newsCopy
    }

下面逐一解释:
1. dispatch sync同步分配到concurrentQueue上执行读取操作。
2. 保存一份新闻的拷贝给newsCopy并返回。

现在这个新闻单例就是线程安全的了。下面我们看一下完整的代码:

import Foundation

class NewsFeed {
    static let sharedInstance = NewsFeed()
    private let concurrentQueue = dispatch_queue_create("newsfeed.queue.concurrent",DISPATCH_QUEUE_CONCURRENT)

    private init() {}

    private var _news: [String] = []
    var news: [String] {
        var newsCopy: [String]!
        dispatch_sync(concurrentQueue){
            newsCopy = self._news
        }
        return newsCopy
    }

    func addNews(n: String) {
        dispatch_barrier_async(concurrentQueue){
            self._news.append(n)
            dispatch_async(dispatch_get_main_queue()){
                self.newsAddedNotification()
            }
        }
    }

    func newsAddedNotification() {
        // post notification
    }
}

欢迎加群互相学习,共同进步。QQ群:iOS: 58099570 | Android: 330987132 | nodejs:329118122 | Go-Scala:217696290 | Python:336880185 | 做人要厚道,转载请注明出处!

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


软件简介:蓝湖辅助工具,减少移动端开发中控件属性的复制和粘贴.待开发的功能:1.支持自动生成约束2.开发设置页面3.做一个浏览器插件,支持不需要下载整个工程,可即时操作当前蓝湖浏览页面4.支持Flutter语言模板生成5.支持更多平台,如Sketch等6.支持用户自定义语言模板
现实生活中,我们听到的声音都是时间连续的,我们称为这种信号叫模拟信号。模拟信号需要进行数字化以后才能在计算机中使用。目前我们在计算机上进行音频播放都需要依赖于音频文件。那么音频文件如何生成的呢?音频文件的生成过程是将声音信息采样、量化和编码产生的数字信号的过程,我们人耳所能听到的声音频率范围为(20Hz~20KHz),因此音频文件格式的最大带宽是20KHZ。根据奈奎斯特的理论,音频文件的采样率一般在40~50KHZ之间。奈奎斯特采样定律,又称香农采样定律。...............
前言最近在B站上看到一个漂亮的仙女姐姐跳舞视频,循环看了亿遍又亿遍,久久不能离开!看着小仙紫姐姐的蹦迪视频,除了一键三连还能做什么?突发奇想,能不能把舞蹈视频转成代码舞呢?说干就干,今天就手把手教大家如何把跳舞视频转成代码舞,跟着仙女姐姐一起蹦起来~视频来源:【紫颜】见过仙女蹦迪吗 【千盏】一、核心功能设计总体来说,我们需要分为以下几步完成:从B站上把小姐姐的视频下载下来对视频进行截取GIF,把截取的GIF通过ASCII Animator进行ASCII字符转换把转换的字符gif根据每
【Android App】实战项目之仿抖音的短视频分享App(附源码和演示视频 超详细必看)
前言这一篇博客应该是我花时间最多的一次了,从2022年1月底至2022年4月底。我已经将这篇博客的内容写为论文,上传至arxiv:https://arxiv.org/pdf/2204.10160.pdf欢迎大家指出我论文中的问题,特别是语法与用词问题在github上,我也上传了完整的项目:https://github.com/Whiffe/Custom-ava-dataset_Custom-Spatio-Temporally-Action-Video-Dataset关于自定义ava数据集,也是后台
因为我既对接过session、cookie,也对接过JWT,今年因为工作需要也对接了gtoken的2个版本,对这方面的理解还算深入。尤其是看到官方文档评论区又小伙伴表示看不懂,所以做了这期视频内容出来:视频在这里:本期内容对应B站的开源视频因为涉及的知识点比较多,视频内容比较长。如果你觉得看视频浪费时间,可以直接阅读源码:goframe v2版本集成gtokengoframe v1版本集成gtokengoframe v2版本集成jwtgoframe v2版本session登录官方调用示例文档jwt和sess
【Android App】实战项目之仿微信的私信和群聊App(附源码和演示视频 超详细必看)
用Android Studio的VideoView组件实现简单的本地视频播放器。本文将讲解如何使用Android视频播放器VideoView组件来播放本地视频和网络视频,实现起来还是比较简单的。VideoView组件的作用与ImageView类似,只是ImageView用于显示图片,VideoView用于播放视频。...
采用MATLAB对正弦信号,语音信号进行生成、采样和内插恢复,利用MATLAB工具箱对混杂噪声的音频信号进行滤波
随着移动互联网、云端存储等技术的快速发展,包含丰富信息的音频数据呈现几何级速率增长。这些海量数据在为人工分析带来困难的同时,也为音频认知、创新学习研究提供了数据基础。在本节中,我们通过构建生成模型来生成音频序列文件,从而进一步加深对序列数据处理问题的了解。
基于yolov5+deepsort+slowfast算法的视频实时行为检测。1. yolov5实现目标检测,确定目标坐标 2. deepsort实现目标跟踪,持续标注目标坐标 3. slowfast实现动作识别,并给出置信率 4. 用框持续框住目标,并将动作类别以及置信度显示在框上
数字电子钟设计本文主要完成数字电子钟的以下功能1、计时功能(24小时)2、秒表功能(一个按键实现开始暂停,另一个按键实现清零功能)3、闹钟功能(设置闹钟以及到时响10秒)4、校时功能5、其他功能(清零、加速、星期、八位数码管显示等)前排提示:前面几篇文章介绍过的内容就不详细介绍了,可以看我专栏的前几篇文章。PS.工程文件放在最后面总体设计本次设计主要是在前一篇文章 数字电子钟基本功能的实现 的基础上改编而成的,主要结构不变,分频器将50MHz分为较低的频率备用;dig_select
1.进入官网下载OBS stdioOpen Broadcaster Software | OBS (obsproject.com)2.下载一个插件,拓展OBS的虚拟摄像头功能链接:OBS 虚拟摄像头插件.zip_免费高速下载|百度网盘-分享无限制 (baidu.com)提取码:6656--来自百度网盘超级会员V1的分享**注意**该插件必须下载但OBS的根目录(应该是自动匹配了的)3.打开OBS,选中虚拟摄像头选择启用在底部添加一段视频录制选择下面,进行录制.
Meta公司在9月29日首次推出一款人工智能系统模型:Make-A-Video,可以从给定的文字提示生成短视频。基于**文本到图像生成技术的最新进展**,该技术旨在实现文本到视频的生成,可以仅用几个单词或几行文本生成异想天开、独一无二的视频,将无限的想象力带入生活
音频信号叠加噪声及滤波一、前言二、信号分析及加噪三、滤波去噪四、总结一、前言之前一直对硬件上的内容比较关注,但是可能是因为硬件方面的东西可能真的是比较杂,而且需要渗透的东西太多了,所以学习进展比较缓慢。因为也很少有单纯的硬件学习研究,总是会伴随着各种理论需要硬件做支撑,所以还是想要慢慢接触理论学习。但是之前总找不到切入点,不知道从哪里开始,就一直拖着。最近稍微接触了一点信号处理,就用这个当作切入点,开始接触理论学习。二、信号分析及加噪信号处理选用了matlab做工具,选了一个最简单的语音信号处理方
腾讯云 TRTC 实时音视频服务体验,从认识 TRTC 到 TRTC 的开发实践,Demo 演示& IM 服务搭建。
音乐音频分类技术能够基于音乐内容为音乐添加类别标签,在音乐资源的高效组织、检索和推荐等相关方面的研究和应用具有重要意义。传统的音乐分类方法大量使用了人工设计的声学特征,特征的设计需要音乐领域的知识,不同分类任务的特征往往并不通用。深度学习的出现给更好地解决音乐分类问题提供了新的思路,本文对基于深度学习的音乐音频分类方法进行了研究。首先将音乐的音频信号转换成声谱作为统一表示,避免了手工选取特征存在的问题,然后基于一维卷积构建了一种音乐分类模型。
C++知识精讲16 | 井字棋游戏(配资源+视频)【赋源码,双人对战】
本文主要讲解如何在Java中,使用FFmpeg进行视频的帧读取,并最终合并成Gif动态图。
在本篇博文中,我们谈及了 Swift 中 some、any 关键字以及主关联类型(primary associated types)的前世今生,并由浅及深用简明的示例向大家讲解了它们之间的奥秘玄机。