Swift多线程:GCD进阶,单例、信号量、任务组

其实这个标题不知道怎么写了,都很碎,也没有想到特别合适的例子能够全部放在一起的。索性就这么平铺开吧。

image.png

1. dispatch_once,以及Swift下的单例

使用dispatch_once函数能保证某段代码在程序运行过程中只被执行1次。所以在通常在OC时代,我们都会用它来写单例。

但是,但是,但是:这个函数在Swift3.0以后的时代已经被删除了。没错,被删除了,不用了。

原来自从Swift 1.x开始Swift就已经开始用dispatch_one机制在后台支持线程安全的全局lazy初始化和静态属性。static var背后已经在使用dispatch_once了,所以从Swift 3开始,就干脆把dispatch_once显式的取消了。

凸(艹皿艹 ),那Swift里面的单例怎么写呐?其实方法有很多种,有OC心Swift皮的写法、新瓶装老酒的写法,那既然咱们开始了Swift,就抛下过去那写沉重包袱吧。这里非典型技术宅只分享其中的一种。

final class SingleTon: NSObject {
    static let shared = SingleTon()
    private override init() {}
}

什么?你在搞事情吧,就这么点?是的,因为是全局变量,所以只会创建一次。

  • 使用final,将这个单例类终止继承。
  • 设置初始化方法为私有,避免外部对象通过访问init方法创建单例类的实例。

2. dispatch_after

在GCD中我们使用dispatch_after()函数来延迟执行队列中的任务。准确的理解是,等到指定的时间到了以后,才会开辟一个新的线程然后立即执行队列中的任务。

所以dispatch_after不会阻塞当前任务,并不是先把任务加到线程里面,等时间到了在执行。而是等时间了,才加入到线程中。

我们使用两种时间格式来看看。

方法一:使用相对时间,DispatchTime

@IBAction func delayProcessDispatchTime(_ sender: Any) {
    //dispatch_time用于计算相对时间,当设备睡眠时,dispatch_time也就跟着睡眠了.
    //Creates a `DispatchTime` relative to the system clock that ticks since boot.
    let time = DispatchTimeInterval.seconds(3)
    let delayTime: DispatchTime = DispatchTime.now() + time
    DispatchQueue.global().asyncAfter(deadline: delayTime) {
        Thread.current.name = "dispatch_time_Thread"
        print("Thread Name: \(String(describing: Thread.current.name))\n dispatch_time: Deplay \(time) seconds.\n")
    }
}

方法二:使用绝对时间,DispatchWallTime

@IBAction func delayProcessDispatchWallTime(_ sender: Any) {
    //dispatch_walltime用于计算绝对时间。
    let delaytimeInterval = Date().timeIntervalSinceNow + 2.0
    let nowTimespec = timespec(tv_sec: __darwin_time_t(delaytimeInterval), tv_nsec: 0)
    let delayWalltime = DispatchWallTime(timespec: nowTimespec)
    
    //wallDeadline需要一个DispatchWallTime类型。创建DispatchWallTime类型,需要timespec的结构体。
    DispatchQueue.global().asyncAfter(wallDeadline: delayWalltime) {
        Thread.current.name = "dispatch_Wall_time_Thread"
        print("Thread Name: \(String(describing: Thread.current.name))\n dispatchWalltime: Deplay \(delaytimeInterval) seconds.\n")
    }
    
}

3. 队列的循环、挂起、恢复

3.1 dispatch_apply

dispatch_apply函数是用来循环来执行队列中的任务的。在Swift 3.0里面对这个做了一些优化,使用以下方法:

public class func concurrentPerform(iterations: Int, execute work: (Int) -> Swift.Void)

本来循环执行就是为了节约时间的嘛,所以默认就是用了并行队列。我们尝试一下用这个升级版的dispatch_apply让它执行10次打印任务。

@IBAction func useDispatchApply(_ sender: Any) {

        print("Begin to start a DispatchApply")
        DispatchQueue.concurrentPerform(iterations: 10) { (index) in
            
            print("Iteration times:\(index),Thread = \(Thread.current)")
        }
        
        print("Iteration have completed.")

}

运行结果如下:

image.png

看,是不是所有的任务都是并行进行的?标红的地方,是非典型技术宅想提醒一下大家这里还是有一些任务是在主线程中进行的。它循环执行并行队列中的任务时,会开辟新的线程,不过有可能会在当前线程中执行一些任务。

如果需要循环的任务里面有特别耗时的操作,我们上一篇文章里面说是应该放在global里面的。如何避免在主线程操作这个呐???

来,给三秒时间想想。
看到调用这个方法的时候是不是就是在UI线程里面这么写下来的嘛?那就开启一个gloablQueue,让它来进行不就好了嘛!BINGO!
这位同学,你已经深得真谛,可以放学后到我家后花园来了。嘿嘿✧(≖ ◡ ≖✿)嘿嘿

3.2 队列的挂起与唤醒

如果一大堆任务执行着的时候,突然后面的任务不想执行的。那怎么办呐?我们可以让它暂时先挂起,等想好了再让它们运行起来。

不过挂起是不会暂停正在执行的队列的哈,只能是挂起还没执行的队列。

@IBAction func useDispatchSuspend(_ sender: Any) {
    let queue = DispatchQueue(label: "new thread")
    //        挂起
    queue.suspend()
    
    queue.async {
        print("The queue is suspended. Now it has completed.\n The queue is \"\(queue.label)\". ")
    }
    
    print("The thread will sleep for 3 seconds' time")
    
    DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + DispatchTimeInterval.seconds(3)) {
        //            唤醒,开始执行
        queue.resume()
    }
}
image.png

我们也可以看一下控制条的打印结果。显然能看到代码并没有按照顺序执行,新建的queue里面的打印是在被唤醒之后才执行的。

4. 信号量(semaphore)

信号量这个东西在之前的文章里面有一个例子里面用到了,当时还有人专门问我semaphore是什么东西。现在可以好好说一说这个了。

不要问我是哪个例子里面用到了,实在想不起来了呀,只能记得有人问过semaphore这个。

有时候多个线程对一个数据进行操作的时候,会造成一些意想不到的效果。多个人同时对同一个数据进行操作,谁知道怎么搞啊!

为了保证同时只有一个线程来修改这个数据,这个时候我们就要用到信号量了。当信号量为0的时候,其他线程想要修改或者使用这个数据就必须要等待了,等待多久呐?DispatchTime.distantFuture,要等待这么久。意思就是一直等待下去。。。。OC里面管这个叫做DISPATCH_TIME_FOREVER

如果给信号量设置成了0,其实就意味着这个资源没有人能够再能用了。所以,当用完了之后一定要把信号量设置成非0( ⊙ o ⊙ )!

//创建一个信号量,初始值为1
let semaphoreSignal = DispatchSemaphore(value: 1)

//表示信号量-1
semaphoreSignal.wait()  

//表示信号量+1
semaphoreSignal.signal() 

4.1 简单实用一下

我们简单的让globalQueue这个全局队列按照1->5的顺序进行打印,打印一次休息1秒钟。

@IBAction func useSemaphore(_ sender: Any) {
    let semaphoreSignal = DispatchSemaphore(value: 1)
    
    for index in 1...5 {
        DispatchQueue.global().async {
            semaphoreSignal.wait()
            print(Thread.current)
            print("这是第\(index)次执行.\n")
            semaphoreSignal.signal()
        }
        print("测试打印")
        
    }
    
}

看一下打印结果:

image.png

globalQueue 如果不加信号量,正常打印是什么样子的?如果不记得,请看上一篇文章。iOS多线程系列之三:使用GCD实现异步下载图片

好奇宝宝们有没有想过,在创建信号量的时候初始值设置成2或者更大的数,例如50,会是什么效果? 自己敲敲代码试试喽,想想看。

4.2 多个线程之间进行任务协调

实际工作中,很多时候我们需要在多个任务之间进行协调,每个任务都是多线程的。

打个比方,我们在后台下载音乐、专辑的封面。等着两个都做完了,才通知用户可以去听音乐了。两个任务都是多线程,我们其实并不知道什么时候才能执行完毕。这个时候,就可以靠信号量,让大家互相等待。

为了更简化这个过程,例子里面模拟了一个在另外一个方法中需要耗时1秒的一个操作。当完成之后,才执行后续操作。

func semaphoreDemo() -> Void {
    let sema = DispatchSemaphore.init(value: 0)
    getListData { (result) in
        if result == true {
            sema.signal()
        }
    }
    sema.wait()
    print("我终于可以开始干活了")
}

private func getListData(isFinish:@escaping (Bool) -> ()) {
    
    DispatchQueue.global().async {
        Thread.sleep(forTimeInterval: 1)
        print("global queue has completed!")
        isFinish(true)
    }
    
}

这个例子不是用group也可以做嘛?!是哒。也可以。

5. 任务组

GCD的任务组在开发中是经常被使用到,当需要一组任务结束后再执行一些操作时,就可以用它啦。

DispatchGroup的职责就是当队列中的所有任务都执行完毕后,会发出一个通知来告诉告诉大家,任务组中所执行的队列中的任务执行完毕了。

既然是组,里面就肯定有很多队列啦,不然怎么能叫做“组”呐。

队列和组关联有两种方式:手动、自动。

5.1 自动关联

肯定先从自动开始了,因为通常自动最省事啊。这还用问嘛。

@IBAction func useGroupQueue(_ sender: UIButton) {
    let group = DispatchGroup()
    //模拟循环建立几个全局队列
    for index in 0...3 {

//创建队列的同时,加入到任务组中        
DispatchQueue.global().async(group: group, execute: DispatchWorkItem.init(block: {
            Thread.sleep(forTimeInterval: TimeInterval(arc4random_uniform(2) + 1))
            print("任务\(index)执行完毕")
        }))
    }
    
    //组中所有任务都执行完了会发送通知
    group.notify(queue: DispatchQueue.main) {
        print("任务组的任务都已经执行完毕啦!")
    }
    
    
    print("打印测试一下")
}

看看打印结果:

image.png

5.2 手动关联

接下来我们将手动的管理任务组与队列中的关系。

enter()leave()是一对儿。前者表示进入到任务组。后者表示离开任务组。

let manualGroup = DispatchGroup()
//模拟循环建立几个全局队列
for manualIndex in 0...3 {
    
    //进入队列管理
    manualGroup.enter()
    DispatchQueue.global().async {
        //让线程随机休息几秒钟
        Thread.sleep(forTimeInterval: TimeInterval(arc4random_uniform(2) + 1))
        print("-----手动任务\(manualIndex)执行完毕")
        
        //配置完队列之后,离开队列管理
        manualGroup.leave()
    }
}

//发送通知
manualGroup.notify(queue: DispatchQueue.main) {
    print("手动任务组的任务都已经执行完毕啦!")
}
image.png

利用任务组可以完成很多场景的工作。例如多任务执行完后,统一刷新UI。把刷新UI的操作放在notify里面就好了。

还记得刷新UI用哪个queue嘛?hoho~

最后,所有的代码都放在这里了:gitHub 下载后给颗Star吧~ 么么哒~(~o ̄3 ̄)~ 爱你们~


iOS多线程系列之一:Operation基础操作,按优先级加载图片
iOS多线程系列之二:Operation实例,异步加载CollectionView图片
iOS多线程系列之三:使用GCD实现异步下载图片
iOS多线程系列之四:GCD进阶,单例、信号量、任务组
iOS多线程系列之五:使用Thread进行多线程间通讯,协调子线程任务

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

推荐阅读更多精彩内容

  • 从哪说起呢? 单纯讲多线程编程真的不知道从哪下嘴。。 不如我直接引用一个最简单的问题,以这个作为切入点好了 在ma...
    Mr_Baymax阅读 2,734评论 1 17
  • 一、前言 上一篇文章iOS多线程浅汇-原理篇中整理了一些有关多线程的基本概念。本篇博文介绍的是iOS中常用的几个多...
    nuclear阅读 2,046评论 6 18
  • 学习多线程,转载两篇大神的帖子,留着以后回顾!第一篇:关于iOS多线程,你看我就够了 第二篇:GCD使用经验与技巧...
    John_LS阅读 606评论 0 3
  • 在这篇文章中,我将为你整理一下 iOS 开发中几种多线程方案,以及其使用方法和注意事项。当然也会给出几种多线程的案...
    张战威ican阅读 601评论 0 0
  • 当我还未降临这个世界,当我的心跳和血液都还属于你的身体的时候,你便开始为我织一件件玲珑温暖的毛衣了,小时候一直穿着...
    庆庆宝贝阅读 403评论 0 0