swift多线程编程之GCD

什么是GCD

  • Grand Central Dispatch 是异步执行任务的技术之一。开发者只需要定义想要执行的任务,并追加到适当的Dispatch Queue中,GCD就能生成必要的线程并执行任务。我们看个经典的例子。
DispatchQueue.global().async {
            
            /*
                这里执行耗时操作
             */
            
            var sum = 0
            for i in 0...100 {
                print("global线程:\(Thread.current)")
                sum += i
            }
            
            /*
             这里刷新UI
             */
            
            DispatchQueue.main.async {
                print("main线程:\(Thread.current)")
                print(sum)
            }
        }
  • Cocoa 框架提供了NSObject类的方法
self.performSelector(inBackground: #selector(doWork), with: nil)

    func doWork() {
        print("后台处理中")
        
        self.performSelector(onMainThread: #selector(finishDone), with: nil, waitUntilDone: true)
    }
    
    func finishDone() {
        print("后台处理完成")
    }

多线程编程

  • 我们知道代码基本上都是从上到下顺序执行代码。如果我们现在有个耗时的操作,那我们不想程序就卡在这里,我们就需要使用异步去处理,去处理下面的问题。或者我们通常添加一个progress提示用户正在处理某些东西。

  • 队列(串行队列,并行队列)

串行队列:单个线程,等待上个任务执行完毕,才往后执行

并行队列:多个线程,可以同时执行

  • 如其名称所示,是执行处理等待的队列。

  • 两个关键队列 main, global
    主队列,和全局队列。我们通常将我们要执行的操作,追加到这个队列后。async, sync 来说明这个block是异步还是同步执行。

  • 测试main, 和global

DispatchQueue.main.async {
            print("当前线程\(Thread.current)------->1")
        }
        DispatchQueue.main.async {
            print("当前线程\(Thread.current)------->2")
        }
        DispatchQueue.main.async {
            print("当前线程\(Thread.current)------->3")
        }
        DispatchQueue.main.async {
            print("当前线程\(Thread.current)------->4")
        }

-打印结果(main队列只有一个线程所以是串行队列)

  1. 当前线程<NSThread: 0x7b257510>{number = 1, name = main}------->1
  2. 当前线程<NSThread: 0x7b257510>{number = 1, name = main}------->2
  3. 当前线程<NSThread: 0x7b257510>{number = 1, name = main}------->3
  4. 当前线程<NSThread: 0x7b257510>{number = 1, name = main}------->4
  • 测试global队列
DispatchQueue.global().async {
            print("当前线程\(Thread.current)------->1")
        }
        DispatchQueue.global().async {
            print("当前线程\(Thread.current)------->2")
        }
        DispatchQueue.global().async {
            print("当前线程\(Thread.current)------->3")
        }
        DispatchQueue.global().async {
            print("当前线程\(Thread.current)------->4")
        }
  • 打印结果(global队列多个线程执行,并行执行是并行队列)
  1. 当前线程<NSThread: 0x7d160c10>{number = 3, name = (null)}------->3
  2. 当前线程<NSThread: 0x7bfa9dd0>{number = 2, name = (null)}------->1
  3. 当前线程<NSThread: 0x7d15de60>{number = 4, name = (null)}------->4
  4. 当前线程<NSThread: 0x7d153b90>{number = 5, name = (null)}------->2

如何创建队列

  • 当然除了系统提供给我们的这两个队列,我们还可以自己创建队列。

  • 串行队列

let queue = DispatchQueue(label: "myQueue")
        queue.async {
            print("当前线程\(Thread.current)------->1")
        }
        queue.async {
            print("当前线程\(Thread.current)------->2")
        }
        queue.async {
            print("当前线程\(Thread.current)------->3")
        }
        queue.async {
            print("当前线程\(Thread.current)------->4")
            }
// 打印结果
1. 当前线程<NSThread:0x7c0825f0>{number = 2, name = (null)}------->1
2. 当前线程<NSThread: 0x7c0825f0>{number = 2, name = (null)}------->2
3. 当前线程<NSThread: 0x7c0825f0>{number = 2, name = (null)}------->3
4. 当前线程<NSThread: 0x7c0825f0>{number = 2, name = (null)}------->4
  • 并行队列
let queue = DispatchQueue(label: "myQueue", qos: DispatchQoS.default, attributes: DispatchQueue.Attributes.concurrent, autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency.workItem, target: nil)
  • qos 队列的优先等级
  1. User Interactive 和用户交互相关,比如动画等等优先级最高。比如用户连续拖拽的计算
  2. User Initiated 需要立刻的结果,比如push一个ViewController之前的数据计算
  3. Utility 可以执行很长时间,再通知用户结果。比如下载一个文件,给用户下载进度。
  4. Background 用户不可见,比如在后台存储大量数据
  • attributes 是并行的还是串行的枚举值(concurrent:并行的, initiallyInactive:串行的)

  • autoreleaseFrequency 队列的释放规则

  • 切换队列

let queue = DispatchQueue(label: "myQueue", qos: DispatchQoS.default, attributes: DispatchQueue.Attributes.concurrent, autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency.workItem, target: nil)
        queue.async {
            print("当前线程\(Thread.current)------->1")
        }
        queue.async {
            print("当前线程\(Thread.current)------->2")
        }
        //切换队列
        queue.setTarget(queue: DispatchQueue.main)
        
        queue.async {
            print("当前线程\(Thread.current)------->3")
        }
        queue.async {
            print("当前线程\(Thread.current)------->4")
        }
  • 执行结果
  1. 当前线程<NSThread: 0x7bc2d970>{number = 2, name = (null)}------->2
  2. 当前线程<NSThread: 0x7b63fe10>{number = 3, name = (null)}------->1
  3. 当前线程<NSThread: 0x7b6215b0>{number = 1, name = main}------->3
  4. 当前线程<NSThread: 0x7b6215b0>{number = 1, name = main}------->4
  • 延时任务
DispatchQueue.main.asyncAfter(deadline: .now() + DispatchTimeInterval.seconds(3)) { //不是三秒后执行该任务,而是三秒后添加到改队列
            //执行的代码
        }
  • 队列组

  • 对于串行队列来说最后一个加入的,肯定是最后一个结束。但是并行队列来说我们怎么知道,这些任务都完成了呐。

  • 看看串行队列的处理方式

let queue = DispatchQueue(label: "myQueue", qos: DispatchQoS.default, attributes: DispatchQueue.Attributes.initiallyInactive, autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency.workItem, target: nil)
        queue.async {
            print("当前线程\(Thread.current)------->1")
        }
        queue.async {
            print("当前线程\(Thread.current)------->2")
        }
        queue.async {
            print("当前线程\(Thread.current)------->3")
        }
        queue.async {
            print("当前线程\(Thread.current)------->4")
            DispatchQueue.main.async {
                print("执行完了")
            }
        }
  • 打印结果
  1. 当前线程<NSThread: 0x7a66cea0>{number = 2, name = (null)}------->1
  2. 当前线程<NSThread: 0x7a66cea0>{number = 2, name = (null)}------->2
  3. 当前线程<NSThread: 0x7a66cea0>{number = 2, name = (null)}------->3
  4. 当前线程<NSThread: 0x7a66cea0>{number = 2, name = (null)}------->4
  5. 执行完了
  • 并行队列我们可以使用group去解决这个问题
let group = DispatchGroup()
        let queue = DispatchQueue(label: "myQueue", qos: DispatchQoS.default, attributes: DispatchQueue.Attributes.concurrent, autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency.workItem, target: nil)
        queue.async(group: group, execute: {
            print("当前线程\(Thread.current)------->1")
        })
        queue.async(group: group, execute: {
            print("当前线程\(Thread.current)------->2")
        })
        queue.async(group: group, execute: {
            print("当前线程\(Thread.current)------->3")
        })
        queue.async(group: group, execute: {
            print("当前线程\(Thread.current)------->4")
        })
        let workItem = DispatchWorkItem { 
            print("执行完成")
        }  group.notify(queue: queue, work: workItem)
  • 打印结果
  1. 当前线程<NSThread: 0x78e49ae0>{number = 2, name = (null)}------->2
  2. 当前线程<NSThread: 0x790a4710>{number = 3, name = (null)}------->1
  3. 当前线程<NSThread: 0x78e53d00>{number = 5, name = (null)}------->3
  4. 当前线程<NSThread: 0x78e43b60>{number = 4, name = (null)}------->4
  5. 执行完成
  • group中wait方法

和notify不同的是wait是等待一段时间,不管队列的任务是否都完成了,都会调用。

  • DispatchWorkItem
let workItem = DispatchWorkItem { 
            print("开始干活\(Thread.current)")
        }
//        workItem.perform() //立即执行, 放到了当前main队列中
//        DispatchQueue.global().async(execute: workItem) //放到global队列中
  • group的enter 和 leave
        let group = DispatchGroup()
        let queue = DispatchQueue(label: "myQueue", qos: DispatchQoS.default, attributes: DispatchQueue.Attributes.concurrent, autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency.workItem, target: nil)
        queue.async(group: group, execute: {
            print("当前线程\(Thread.current)------->1")
            queue.async(execute: {
                print("当前线程\(Thread.current)------->2")
            })
            queue.async(execute: {
                print("当前线程\(Thread.current)------->3")
            })
        })
        let workItem = DispatchWorkItem {
            print("done")
        }
        group.notify(queue: queue, work: workItem)
  • 打印结果
  1. 当前线程<NSThread: 0x7b967590>{number = 2, name = (null)}------->1
  2. done
  3. 当前线程<NSThread: 0x797807d0>{number = 1, name = main}------->2
  4. 当前线程<NSThread: 0x7b8bd8b0>{number = 3, name = (null)}------->3
  • 我们试想当我们的并行队列里还有其他的并行队列,我们想等他们都操作完之后,在执行下一步,该怎么办。
let group = DispatchGroup()
        let queue = DispatchQueue(label: "myQueue", qos: DispatchQoS.default, attributes: DispatchQueue.Attributes.concurrent, autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency.workItem, target: nil)
        queue.async(group: group, execute: {
            print("当前线程\(Thread.current)------->1")
            
            group.enter()
            queue.async(execute: {
                group.leave()
                print("当前线程\(Thread.current)------->2")
            })
            
            group.enter()
            queue.async(execute: {
                print("当前线程\(Thread.current)------->3")
                group.leave()
            })
            
        })
        let workItem = DispatchWorkItem {
            print("done")
        }
        group.notify(queue: queue, work: workItem)
  1. 当前线程<NSThread: 0x7a95e9e0>{number = 2, name = (null)}------->1
  2. 当前线程<NSThread: 0x7a95e9e0>{number = 2, name = (null)}------->2
  3. 当前线程<NSThread: 0x7a963a70>{number = 3, name = (null)}------->3
  4. done
  • 并行队列处理数据的时候出现的问题
var array: [Int] = []
        
        for i in 0...100 {
            DispatchQueue.global().async(execute: { 
                array.append(i)
            })
        }
  • 多个线程去更新array时就会出现内存错误。同一个线程访问了同一块地址内存,同时往里面进行写数据。
Demo(61868,0x38bc1c0) malloc: *** error for object 0x7ac34424: incorrect checksum for freed object - object was probably modified after being freed.
*** set a breakpoint in malloc_error_break to debug
(lldb)
  1. 使用信号量解决
var array: [Int] = []
        let semaphore = DispatchSemaphore(value: 1)
        for i in 0...100 {
            DispatchQueue.global().async(execute: {
                
                /*
                    wait 等待信号量semaphore >= 1时执行
                    wait返回时 semaphore 减1
                 */
                _ = semaphore.wait(wallTimeout: .distantFuture)
                
                array.append(i) //只会同时被一个线程访问
                
                /*
                 signal semaphore 加1
                 */
                semaphore.signal()
            })
        }
        DispatchQueue.main.async {
            print(array.count)
        }
  • 使用互斥锁
let lock = NSLock()
        var array: [Int] = []
        
        for i in 0...100 {
            DispatchQueue.global().async(execute: {
                lock.lock()
                array.append(i)
                lock.unlock()
            })
        }
        DispatchQueue.main.async {
            print(array.count)
        }

死锁(线程之间互相等待对方执行完之后才能接着执行)

  • main队列里执行下面代码造成死锁。使用sync编程时要注意死锁的发生。
DispatchQueue.main.sync {
            print("死锁了")
        }

DispatchSource 调度源

它的作用是当有一些特定的较底层的系统事件发生时,调度源会捕捉到这些事件,然后可以做其他的逻辑处理,调度源有多种类型,分别监听对应类型的系统事件。我们来看看它都有哪些类型
  1. DISPATCH_SOURCE_TYPE_DATA_ADD:属于自定义事件,可以通过dispatch_source_get_data函数获取事件变量数据,在我们自定义的方法中可以调用dispatch_source_merge_data函数向Dispatch Source设置数据,下文中会有详细的演示。
  2. DISPATCH_SOURCE_TYPE_DATA_OR:属于自定义事件,用法同上面的类型一样。
  3. DISPATCH_SOURCE_TYPE_MACH_SEND:Mach端口发送事件。
  4. DISPATCH_SOURCE_TYPE_MACH_RECV:Mach端口接收事件。
  5. DISPATCH_SOURCE_TYPE_PROC:与进程相关的事件。
  6. DISPATCH_SOURCE_TYPE_READ:读文件事件。
  7. DISPATCH_SOURCE_TYPE_WRITE:写文件事件。
  8. DISPATCH_SOURCE_TYPE_VNODE:文件属性更改事件。
  9. DISPATCH_SOURCE_TYPE_SIGNAL:接收信号事件。
  10. DISPATCH_SOURCE_TYPE_TIMER:定时器事件。
  11. DISPATCH_SOURCE_TYPE_MEMORYPRESSURE:内存压力事件。
  • 看例子每隔一秒执行操作
private var timer: DispatchSourceTimer!

        self.timer = DispatchSource.makeTimerSource(flags: DispatchSource.TimerFlags.strict, queue: DispatchQueue.global())
        self.timer.scheduleRepeating(deadline: .now(), interval: DispatchTimeInterval.seconds(1)) //每隔一秒执行下面的操作
        self.timer.setEventHandler {
            DispatchQueue.main.async(execute: {
                //回到主线程刷新UI
                print(Date())
            })
        }
        self.timer.resume()
  • 你可以使用改装一下做个倒计时。当获取激活码时。

  • 假如我们有5个线程去下载,我们想知道这五个线程的下载进度。

private var dispatchDataSource: DispatchSourceUserDataAdd!

        var progress: UInt = 0
        self.dispatchDataSource = DispatchSource.makeUserDataAddSource(queue: DispatchQueue.main)
        self.dispatchDataSource.setEventHandler { 
            let data = UInt(self.dispatchDataSource.data) //获取数据中心的data
            progress += data
            print("上传任务进度:\(progress)")
        }
        self.dispatchDataSource.resume()
        
        for _ in 0...5 {
            DispatchQueue.global().async(execute: {
                print("异步处理任务")
                
                self.dispatchDataSource.add(data: 1) //将数组添加数据中心
            })
        }
  • 打印结果
异步处理任务
异步处理任务
异步处理任务
异步处理任务
异步处理任务
异步处理任务
上传任务进度:6
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容