[Swift]多线程--GCD

1. 前言

之前写了一遍文章介绍了Objective-C中GCD的用法: [iOS]多线程--GCD, 现在转战到Swift的战场, 根据前者一些知识点, 讲下使用Swift如何实现.

2. 队列

2.1 串行队列

创建串行队列的方式很简单:

DispatchQueue(label: <#T##String#>)
  • label : 队列的标识符

例如:

let queue = DispatchQueue(label: "QueueIdentifier")

如果想指定串行队列的优先级, 可使用下面的方法来创建:

let queue = DispatchQueue(label: "QueueIdentifier", qos: .userInitiated)

参数qos: 用于指定队列的优先级, 是个枚举:

  • .userInteractive
  • .userInitiated
  • .default
  • .utility
  • .background
  • .unspecified
    优先级, 由上往下依次降低

一个最常用的串行队列--主队列:

DispatchQueue.main

2.2 并行队列

并行队列, 可以使用下面这个方法进行创建:

DispatchQueue(label: <#T##String#>, qos: <#T##DispatchQoS#>, attributes: <#T##DispatchQueue.Attributes#>)

和创建串行队列的方法一样, 只不过多了一个参数attributes, 传 .concurrent 即可创建一个并行队列

let queue1 = DispatchQueue(label: "并行队列的创建", qos: .default, attributes: .concurrent)

在使用的时候, 我们一般不去创建并行队列, 而是使用系统为我们提供的全局的并行队列:

// 获取全局并行队列
let queue = DispatchQueue.global()

在获取的时候我们也可以指定其优先级:

let que = DispatchQueue.global(qos: .default)

注意:
在创建串行并行队列的时候, 参数attributes, 可以指定创建的是串行还是并行队列, 他还有一个值: .initiallyInactive, 即: 创建的时候, 是处于不活跃状态, 即不会执行任务, 需要手动调用activate()来激活队列执行任务;

例如:

print("当前线程: \(Thread.current)")
        let queue = DispatchQueue(label: "开始不活跃的串行队列", attributes: .initiallyInactive)
        queue.async {
            print("执行了么?")
        }
        print("任务结束")

这个示例, 程序会crash, 应该这样:

print("当前线程: \(Thread.current)")
        let queue = DispatchQueue(label: "开始不活跃的串行队列", attributes: .initiallyInactive)
            queue.async {
            print("执行了么?")
        }
        queue.activate()
        print("任务结束")

上面这个是, 串行的不活跃队列, 如果想创建并行的不活跃队列呢? 可以这样:

print("当前线程: \(Thread.current)")
let queue = DispatchQueue(label: "开始不活跃的并行队列", attributes: [.concurrent, .initiallyInactive])
queue.async {
            print("执行了么?")
        }
        queue.activate()
        print("任务结束")

2.3 同步, 异步

同步/异步的执行, 只需要使用队列的实例对象调用sync(同步)/async(异步)方法即可, 这里可以使用闭包, 也可以使用DispatchWorkItem对象

queue.sync {
            <#code#>
        }
queue.async(execute: <#T##DispatchWorkItem#>)

3. 同步, 异步, 串行, 并发组合测试

测试一: 用同步函数往串行队列中添加任务

不会开启新的线程:

print("当前线程: \(Thread.current)")
       
        let queue = DispatchQueue(label: "创建串行队列")
        
        queue.sync {
            print("串行队列中同步执行的第1个任务: \(Thread.current)")
            sleep(4)
        }
        
        queue.sync {
            print("串行队列中同步执行的第2个任务: \(Thread.current)")
            sleep(2)
        }
        
        queue.sync {
            print("串行队列中同步执行的第3个任务: \(Thread.current)")
        }

控制台输出:

当前线程: <NSThread: 0x600000065900>{number = 1, name = main}
串行队列中同步执行的第1个任务: <NSThread: 0x600000065900>{number = 1, name = main}
串行队列中同步执行的第2个任务: <NSThread: 0x600000065900>{number = 1, name = main}
串行队列中同步执行的第3个任务: <NSThread: 0x600000065900>{number = 1, name = main}

可以看出, 没有开启新的线程, 同时也是按照顺序依次执行的;

测试二: 用异步函数往串行队列中添加任务

会开启线程,但是只开启一个线程:

print("当前线程: \(Thread.current)")
        
        
        let queue = DispatchQueue(label: "创建串行队列")
        
        queue.async {
            print("串行队列中同步执行的第1个任务: \(Thread.current)")
            sleep(4)
        }
        
        queue.async {
            print("串行队列中同步执行的第2个任务: \(Thread.current)")
            sleep(2)
        }
        
        queue.async {
            print("串行队列中同步执行的第3个任务: \(Thread.current)")
        }

控制台输出:

当前线程: <NSThread: 0x608000064000>{number = 1, name = main}
串行队列中同步执行的第1个任务: <NSThread: 0x608000071dc0>{number = 4, name = (null)}
串行队列中同步执行的第2个任务: <NSThread: 0x608000071dc0>{number = 4, name = (null)}
串行队列中同步执行的第3个任务: <NSThread: 0x608000071dc0>{number = 4, name = (null)}

虽然是异步函数, 但是添加到了串行队列里, 只开启了一个新的线程, 添加到其中的任务还是按顺序依次执行的

测试三: 用同步函数往并发队列中添加任务

不会开启新的线程((同步函数不具备开启新线程的能力)),并发队列失去了并发的功能:

 print("当前线程: \(Thread.current)")
        
        
        let queue = DispatchQueue(label: "创建并行队列", attributes: .concurrent)
        
        queue.sync {
            print("串行队列中同步执行的第1个任务: \(Thread.current)")
            sleep(4)
        }
        
        queue.sync {
            print("串行队列中同步执行的第2个任务: \(Thread.current)")
            sleep(2)
        }
        
        queue.sync {
            print("串行队列中同步执行的第3个任务: \(Thread.current)")
        }

控制台输出:

当前线程: <NSThread: 0x600000077b80>{number = 1, name = main}
串行队列中同步执行的第1个任务: <NSThread: 0x600000077b80>{number = 1, name = main}
串行队列中同步执行的第2个任务: <NSThread: 0x600000077b80>{number = 1, name = main}
串行队列中同步执行的第3个任务: <NSThread: 0x600000077b80>{number = 1, name = main}

可以看出, 虽然使用的是并发队列, 但是使用的是同步函数, 由于同步函数没有开启新线程的能力, 所以并发队列就失去了并发性, 按照任务的添加顺序, 顺序执行;

测试四. 用异步函数往并发队列中添加任务

同时开启多个子线程执行任务:

 print("当前线程: \(Thread.current)")
        
        
        let queue = DispatchQueue(label: "创建并行队列", attributes: .concurrent)
        
        queue.async {
            print("串行队列中同步执行的第1个任务: \(Thread.current)")
            sleep(4)
        }
        
        queue.async {
            print("串行队列中同步执行的第2个任务: \(Thread.current)")
            sleep(2)
        }
        
        queue.async {
            print("串行队列中同步执行的第3个任务: \(Thread.current)")
        }

控制台输出:

当前线程: <NSThread: 0x60000007c6c0>{number = 1, name = main}
串行队列中同步执行的第2个任务: <NSThread: 0x600000270380>{number = 4, name = (null)}
串行队列中同步执行的第3个任务: <NSThread: 0x60800027dec0>{number = 5, name = (null)}
串行队列中同步执行的第1个任务: <NSThread: 0x60000026b600>{number = 3, name = (null)}

可以看出, 这里开启了三个子线程来执行任务, 互相之间没有影响.
只有在并发队列异步执行的时候才能真正起到 并发的作用.

测试五. 控制最大并发数

在进行并发操作的时候, 如果任务过多, 开启很多线程, 会导致APP卡死. 所以, 我们要控制最大并发数, 这就用到了信号量DispatchSemaphore, 我们可以这样创建一个信号量:

let semaphore = DispatchSemaphore.init(value: 10)

参数为最大并发执行的任务数, 也即是信号量.
信号量减一:

semaphore.wait()
// 如果信号量大于1, 则会继续执行, 如果信号量等于0, 会等待timeout的时间, 在等待期间被semaphore.signal()加一了, 这里会继续执行, 并将信号量减一
semaphore.wait(timeout: <#T##DispatchTime#>)

上面函数的返回值为DispatchTimeoutResult, 是个枚举:

  • success
  • timedOut

DispatchTime的值可使用: .now() , 或者 .distantFuture
也可以创建:

DispatchTime.init(uptimeNanoseconds: <#T##UInt64#>)

这里的参数单位为纳秒: 1s = 1000*1000*100ns

信号量加一:

semaphore.signal()

一个应用:

print("当前线程: \(Thread.current)")
        
        let group = DispatchGroup.init()
        let queue = DispatchQueue.global()
        
        let semaphore = DispatchSemaphore.init(value: 10)
        
        for i in 0...100 {
            
            let result = semaphore.wait(timeout: .distantFuture)
            if result == .success {
                
                queue.async(group: group, execute: {
                    
                    print("队列执行\(i)--\(Thread.current)")
                    // 模拟执行任务时间
                    sleep(2)
                    // 任务结束, 信号量+1
                    semaphore.signal()
                })
            }
            
        }
        
        group.wait()

这个示例就是每十个任务并发执行.

测试六: 使用DispatchWorkItem
print("当前线程: \(Thread.current)")
        
        // 新建一个任务
        let workItem = DispatchWorkItem { 
            
            print("执行一个任务\(Thread.current)")
            sleep(3)
        }
        // 在当前线程执行任务
        workItem.perform()
       
        // 执行完成后, 通知主队列
        workItem.notify(queue: DispatchQueue.main) { 
            
            print("任务完成了")
        }

输出:

当前线程: <NSThread: 0x6000000774c0>{number = 1, name = main}
执行一个任务<NSThread: 0x6000000774c0>{number = 1, name = main}
任务完成了

在另一个队列执行任务(异步):

 print("当前线程: \(Thread.current)")
        
        // 新建一个任务
        let workItem = DispatchWorkItem { 
            
            print("执行一个任务\(Thread.current)")
            sleep(3)
        }
        
        let queue = DispatchQueue.global()
        // 执行任务
        queue.async(execute: workItem)
        // 执行完成后, 通知主队列
        workItem.notify(queue: DispatchQueue.main) { 
            
            print("任务完成了")
        }
        
        print("任务结束")

输出:

当前线程: <NSThread: 0x608000261700>{number = 1, name = main}
任务结束
执行一个任务<NSThread: 0x6080004661c0>{number = 4, name = (null)}
任务完成了

在创建任务的时候, 可使用下面的参数来设置其优先级:

let workItem = DispatchWorkItem(qos: <#T##DispatchQoS#>, block: <#T##() -> Void#>)

4. 一些应用

4.1延迟执行
print("当前线程: \(Thread.current)")
        print("开始时间\(Date())")
        let delayQueue = DispatchQueue(label: "delayQueue")
        // 延迟 2s
        let delayTime = DispatchTimeInterval.seconds(2)
        
        delayQueue.asyncAfter(deadline: .now() + delayTime) {
            print("这是延迟2s后执行的任务, 结束时间\(Date())")
            
        }

输出:

当前线程: <NSThread: 0x608000077d80>{number = 1, name = main}
开始时间2017-05-17 06:52:03 +0000
任务结束
这是延迟2s后执行的任务, 结束时间2017-05-17 06:52:05 +0000

这里的时间有以下几种方式设置:

let delayTime = DispatchTimeInterval.seconds(2)// 秒
let delayTime = DispatchTimeInterval.milliseconds(2*1000)// 毫秒
let delayTime = DispatchTimeInterval.microseconds(2*1000*1000)// 微秒
let delayTime = DispatchTimeInterval.nanoseconds(2*1000*1000*1000)// 纳秒

这里都是设置的延迟2s
也可以如下, 直接加上要延迟的时间间隔:

print("当前线程: \(Thread.current)")
        print("开始时间\(Date())")
        let delayQueue = DispatchQueue(label: "delayQueue")
        // 延迟 2s
        delayQueue.asyncAfter(deadline: .now() + 2) {
            print("这是延迟2s后执行的任务, 结束时间\(Date())")
        }
        print("任务结束")
4.2 汇总执行

如果, 你想某个任务在其他任务执行之后再执行, 或者必须某个任务执行完,才能执行下面的任务, 可以使用DispatchGroup:

print("当前线程: \(Thread.current)")
        let queue = DispatchQueue(label: "queueName", attributes: .concurrent)
        
        queue.async {
            sleep(4)
            print("任务 1")
        }
        
        queue.async {
            sleep(2)
            print("任务 2")
        }
        
        queue.async {
            sleep(6)
            print("任务 3")
        }
        
        DispatchGroup.init().notify(qos: .default, flags: .barrier, queue: queue) { 
            
            print("所有任务结束")
        }
        print("任务结束")
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容