iOS 多线程 GCD (Swift 3)

0. 前言

GCD(Grand Central Dispatch)是 Apple 开发的一种多核编程的方法,主要的作用是优化应用程序对多核处理器的运用,是在线程池模式的基础上执行的并行任务。GCD中有两个重要的基本概念——任务队列


1. 任务和队列

任务

就是执行的操作,也就是在线程中执行的代码,封装在 Swift 的闭包(Closure)或者 OC 的块(block)里。根据能否具备开启新线程可以分成 同步执行异步执行

  • 同步执行(sync) 只能在当前线程中执行任务,不能开新线程,当前的代码段没有执行完就不能够执行下一部分,会阻塞当前线程。
  • 异步执行(async) 可以在新线程中执行任务,能够开启新线程,不用等待当前的代码段执行完就可以往下执行后续代码,不会阻塞当前线程。

队列

就是任务队列,是一种特殊的线性表,满足 FIFO 的原则。在 GCD 里分为 串行队列并发队列

  • 串行队列(Serial Dispatch Queue) 一个任务完成后接着执行下一个任务。
  • 并发队列(Concurrent Dispatch Queue) 自动开启多个线程进行多个任务并发执行,而且并发只有在异步执行(dispatch_async)情况下才有效。

2. 创建队列

系统默认队列——主队列(串行)

//主队列
let mainQueue = DispatchQueue.main

系统默认队列——全局队列(并发)

//全局队列
let globalQueue = DispatchQueue.global()

全局队列有 4 个执行的优先级,从高到低分别是:
.userInitiated.default.utility.background
所以当需要创建指定某个优先级的全局队列时:

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

自定义创建队列

//创建串行队列
let serialQueue = DispatchQueue(label: "serialQueue_1")
//创建优先级为default的串行队列
let serialQueue = DispatchQueue(label: "serialQueue_2", qos: .default)

//创建优先级为background的并发队列
let concurrentQueue = DispatchQueue(label: "concurrentQueue", qos: .background, attributes: .concurrent)

队列执行

队列的执行部分可以用 3 个任务来反映出任务执行的顺序。

同步并行

代码示例:

func syncConcurrent() {

  let queue = DispatchQueue(label: "queue", attributes: .concurrent)

  print("--- begin ---")

  queue.sync {
      for i in 1...3 {
          print("--- 🅰️ \(i) ---\(Thread.current)")
      }
  }

  queue.sync {
      for i in 1...3 {
          print("--- 🅱️ \(i) ---\(Thread.current)")
      }
  }

  queue.sync {
      for i in 1...3 {
          print("--- 🅾️ \(i) ---\(Thread.current)")
      }
  }

  print("--- end ---")

}

打印结果

--- begin ---
--- 🅰️ 1 ---<NSThread: 0x60400006f380>{number = 1, name = main}
--- 🅰️ 2 ---<NSThread: 0x60400006f380>{number = 1, name = main}
--- 🅰️ 3 ---<NSThread: 0x60400006f380>{number = 1, name = main}
--- 🅱️ 1 ---<NSThread: 0x60400006f380>{number = 1, name = main}
--- 🅱️ 2 ---<NSThread: 0x60400006f380>{number = 1, name = main}
--- 🅱️ 3 ---<NSThread: 0x60400006f380>{number = 1, name = main}
--- 🅾️ 1 ---<NSThread: 0x60400006f380>{number = 1, name = main}
--- 🅾️ 2 ---<NSThread: 0x60400006f380>{number = 1, name = main}
--- 🅾️ 3 ---<NSThread: 0x60400006f380>{number = 1, name = main}
--- end ---

任务是按照顺序逐个执行的,并且只有一个主线程在战斗

异步并行

代码示例

func asyncConcurrent() {

    let queue = DispatchQueue(label: "queue", attributes: .concurrent)

    print("--- begin ---")

    queue.async {
        for i in 1...3 {
            print("--- 🅰️ \(i) ---\(Thread.current)")
        }
    }

    queue.async {
        for i in 1...3 {
            print("--- 🅱️ \(i) ---\(Thread.current)")
        }
    }

    queue.async {
        for i in 1...3 {
            print("--- 🅾️ \(i) ---\(Thread.current)")
        }
    }

    print("--- end ---")

}

打印结果 1

--- begin ---
--- 🅰️ 1 ---<NSThread: 0x604000078bc0>{number = 3, name = (null)}
--- 🅱️ 1 ---<NSThread: 0x6080000747c0>{number = 4, name = (null)}
--- 🅰️ 2 ---<NSThread: 0x604000078bc0>{number = 3, name = (null)}
--- 🅾️ 1 ---<NSThread: 0x6040000793c0>{number = 5, name = (null)}
--- 🅱️ 2 ---<NSThread: 0x6080000747c0>{number = 4, name = (null)}
--- 🅰️ 3 ---<NSThread: 0x604000078bc0>{number = 3, name = (null)}
--- end ---
--- 🅾️ 2 ---<NSThread: 0x6040000793c0>{number = 5, name = (null)}
--- 🅱️ 3 ---<NSThread: 0x6080000747c0>{number = 4, name = (null)}
--- 🅾️ 3 ---<NSThread: 0x6040000793c0>{number = 5, name = (null)}

打印结果 2

--- begin ---
--- 🅰️ 1 ---<NSThread: 0x604000069140>{number = 3, name = (null)}
--- 🅱️ 1 ---<NSThread: 0x60800006aa40>{number = 4, name = (null)}
--- 🅰️ 2 ---<NSThread: 0x604000069140>{number = 3, name = (null)}
--- 🅱️ 2 ---<NSThread: 0x60800006aa40>{number = 4, name = (null)}
--- 🅾️ 1 ---<NSThread: 0x60800006a900>{number = 5, name = (null)}
--- 🅰️ 3 ---<NSThread: 0x604000069140>{number = 3, name = (null)}
--- end ---
--- 🅾️ 2 ---<NSThread: 0x60800006a900>{number = 5, name = (null)}
--- 🅱️ 3 ---<NSThread: 0x60800006aa40>{number = 4, name = (null)}
--- 🅾️ 3 ---<NSThread: 0x60800006a900>{number = 5, name = (null)}

每一次运行的执行顺序不一定相同,任务的执行是交替的,一共新开了 3 个线程在战斗。并且从 begin 和 end 的位置可以看出,3 个任务被添加到队列之后就立刻开始了战斗。

同步串行

func syncSerial() {

    let queue = DispatchQueue(label: "queue")

    print("--- begin ---")

    queue.sync {
        for i in 1...3 {
            print("--- 🅰️ \(i) ---\(Thread.current)")
        }
    }

    queue.sync {
        for i in 1...3 {
            print("--- 🅱️ \(i) ---\(Thread.current)")
        }
    }

    queue.sync {
        for i in 1...3 {
            print("--- 🅾️ \(i) ---\(Thread.current)")
        }
    }

    print("--- end ---")

}

打印结果
--- begin ---
--- 🅰️ 1 ---<NSThread: 0x6000000777c0>{number = 1, name = main}
--- 🅰️ 2 ---<NSThread: 0x6000000777c0>{number = 1, name = main}
--- 🅰️ 3 ---<NSThread: 0x6000000777c0>{number = 1, name = main}
--- 🅱️ 1 ---<NSThread: 0x6000000777c0>{number = 1, name = main}
--- 🅱️ 2 ---<NSThread: 0x6000000777c0>{number = 1, name = main}
--- 🅱️ 3 ---<NSThread: 0x6000000777c0>{number = 1, name = main}
--- 🅾️ 1 ---<NSThread: 0x6000000777c0>{number = 1, name = main}
--- 🅾️ 2 ---<NSThread: 0x6000000777c0>{number = 1, name = main}
--- 🅾️ 3 ---<NSThread: 0x6000000777c0>{number = 1, name = main}
--- end ---

打印结果和同步并行一个样,都是只有主线程在战斗,并且按照顺序逐一执行。

串行异步

代码示例

func asyncSerial() {

    let queue = DispatchQueue(label: "queue")

    print("--- begin ---")

    queue.async {
        for i in 1...3 {
            print("--- 🅰️ \(i) ---\(Thread.current)")
        }
    }

    queue.async {
        for i in 1...3 {
            print("--- 🅱️ \(i) ---\(Thread.current)")
        }
    }

    queue.async {
        for i in 1...3 {
            print("--- 🅾️ \(i) ---\(Thread.current)")
        }
    }

    print("--- end ---")

}

打印结果

--- begin ---
--- 🅰️ 1 ---<NSThread: 0x60400007a700>{number = 3, name = (null)}
--- 🅰️ 2 ---<NSThread: 0x60400007a700>{number = 3, name = (null)}
--- 🅰️ 3 ---<NSThread: 0x60400007a700>{number = 3, name = (null)}
--- end ---
--- 🅱️ 1 ---<NSThread: 0x60400007a700>{number = 3, name = (null)}
--- 🅱️ 2 ---<NSThread: 0x60400007a700>{number = 3, name = (null)}
--- 🅱️ 3 ---<NSThread: 0x60400007a700>{number = 3, name = (null)}
--- 🅾️ 1 ---<NSThread: 0x60400007a700>{number = 3, name = (null)}
--- 🅾️ 2 ---<NSThread: 0x60400007a700>{number = 3, name = (null)}
--- 🅾️ 3 ---<NSThread: 0x60400007a700>{number = 3, name = (null)}

任务是按顺序逐一进行的,开启了一条新的线程。

主队列和全局队列的同步/异步

同步

DispatchQueue.global(qos: .default).async {
    print("I'm in global.")
    DispatchQueue.main.async {
        print("I'm back in main.")
    }
}

异步

//Global Dispatch Queue
//不会死锁,但代码顺序执行,后续代码需要等待前面代码执行完毕
DispatchQueue.global(qos: .default).sync {
    print("I'm in global.")
    print("I'm still in global.")
}
print("I'm out.")

//Main Queue
//死锁。按照同步的执行顺序,一个任务需要等待前一个任务执行完毕,但是"I'm in main."是添加在"I'm out."之后,但是"I'm out"又需要等待"I'm in main."执行完毕之后再执行。所以彼此等待造成了死锁。
DispatchQueue.main.sync {
    print("I'm in main.")
}
print("I'm out.")

队列暂停和继续

//暂停
queue.suspend()
//继续
queue.resume()

suspend() 和 resume() 是异步函数,在两个闭包之间生效。
suspend() 会使得已经添加到 Dispatch Queue 但是还没有执行的任务在改行代码之后暂停执行,等到 resume() 之后才能继续执行。

单次执行

单次执行在多线程编程的使用过程中很常见,当然除了多线程之外也有一些其他的用途。
在 Swift 3 之前,单次执行可以通过 dispatch_once 来实现,但是 Swift 3 把它废弃了... 查了一些资料发现要实现原来的 dispatch_once 可以通过下面几个方式:

  1. 全局常量
let constant = SomeClass()
  1. 全局变量(带有立即执行的闭包构造器)
var variable: SomeClass = {
    let constant = SomeClass()
    constant.oneProperty = "HeySiri"
    constant.oneMethod()
    return constant
}()

3. 未完待续...

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

推荐阅读更多精彩内容