多线程总结之GCD

任务:任务是指执行什么样的操作。

队列:用来存放任务,分为串行队列和并行队列。串行队列:当前任务执行完成之后再执行下一个任务;并行队列:不等待当前任务执行完,下一个任务并发执行

线程:执行任务需要线程,线程从队列中以先进先出(FIFO)的方式取出任务执行,线程一次只能执行一个任务

在GCD中,任务可看作是代码块(block),执行这些任务、同步还是异步执行,由GCD中的调度函数以及队列的类型来决定。

GCD中的队列:

dispatch_main_queue   主队列,程序启动时与主线程一起由系统自动创建,UI操作都放在主队列中

dispatch_get_global_queue  全局并发队列,由系统定义,调用函数dispatch_get_global_queue(identifier: Int, flags: Uint)获取。identifier以前叫做优先级,现在称为服务质量,现在传入优先级的宏定义也可以,目测优先级的界限并不明显,所以平时直接传入0,见下面对应关系。flags是系统保留的一个参数,传入0即可。

DISPATCH_QUEUE_PRIORITY_HIGH:         QOS_CLASS_USER_INITIATED       2

DISPATCH_QUEUE_PRIORITY_DEFAULT:      QOS_CLASS_DEFAULT              0

DISPATCH_QUEUE_PRIORITY_LOW:          QOS_CLASS_UTILITY             -2

DISPATCH_QUEUE_PRIORITY_BACKGROUND:   QOS_CLASS_BACKGROUND           INT16_MIN

对于同一优先级,获取到的全局并发队列是同一个。看下面获取全局并发队列

let queueDefault1 =dispatch_get_global_queue(0,0)

let queueDefault2 = dispatch_get_global_queue(0,0)

let queueHigh3 = dispatch_get_global_queue(2,0)

let queueHigh4 = dispatch_get_global_queue(2,0)

print(queueDefault1)

print(queueDefault2)

print(queueHigh3)

print(queueHigh4)

运行结果:

<OS_dispatch_queue_root: com.apple.root.default-qos[0x112602240]>

<OS_dispatch_queue_root: com.apple.root.default-qos[0x112602240]>

<OS_dispatch_queue_root: com.apple.root.user-initiated-qos[0x1126023c0]>

<OS_dispatch_queue_root: com.apple.root.user-initiated-qos[0x1126023c0]>


dispatch_queue_create(label: UnsafePointer<Int8>, attr: dispatch_queue_attr_t!)  自己创建队列。label是该队列的名字,可以调用dispatch_get_queue_label(queue: dispatch_queue_t!) 获取。attr传入DISPATCH_QUEUE_SERIAL(创建串行队列,可用nil替代)或DISPATCH_QUEUE_CONCURRENT(创建并发队列)

dispatch_queue_create 创建的优先级与全局并发队列的默认优先级是同一级别,也就是说创建的队列的优先级为默认优先级。至于怎么改变它的优先级需要调用dispatch_set_target_queue,这个函数后面描述。


执行任务的调度函数:

dispatch_sync       将任务提交到相应队列,同步执行,不开新线程

dispatch_async     将任务提交到相应队列,异步执行,如果从主队列取任务,不开新线程,在主线程中执行;其他队列,开新线程执行

现在来看看调度函数与各种队列的组合后的情况:

1、dispatch_sync 与 串行队列:

如果,在串行队列queue中调用该函数、且该函数是从串行队列queue中去任务执行,就会出现线程死锁。

        例如下面两种情况:

    (1)在主线程中调用

dispatch_sync(dispatch_get_main_queue()) {  print("在主队列中执行任务")  }

  (2)在同步执行任务的子线程中调用

let queue = dispatch_queue_create("queue1",DISPATCH_QUEUE_SERIAL)          

dispatch_async(queue) {

      /*

      执行操作

       */

     dispatch_sync(queue, {

         print("在当前队列中执行操作")

     })

    }


 如果该串行队列与当前队列不同,不会出现线程死锁,在当前线程中同步执行,也就是说此时调用该函数没有意义


2、dispatch_sync 与 并行队列:

  因为dispatch_sync不开线程,并且线程一次只能执行一个任务,任务在当前线程中没执行完,下一个任务就没法执行。也就是说,队列中的任务是顺序执行的,此时调用该函数也没有意义。


3、dispatch_async 与 串行队列:

   (1)如果是主队列,不开新线程,任务将在主线程中执行。但不会马上执行,而是将任务从栈copy到堆,等待主队列中栈区的任务执行完,再执行堆区的任务

   (2)创建的串行队列,开新线程同步执行


4、dispatch_async 与 并发队列:

    开新线程并发执行任务,至于开多少条线程由系统决定,例如:

for iin1...20 {

   dispatch_async(dispatch_get_global_queue(0,0)) {

      print("线程\(NSThread.currentThread())执行第\(i)个任务")

    }

}

   运行效果:

线程<NSThread: 0x7fab94900570>{number = 6, name = (null)}执行第8个任务

线程<NSThread: 0x7fab94900850>{number = 8, name = (null)}执行第3个任务

线程<NSThread: 0x7fab926099e0>{number = 3, name = (null)}执行第2个任务

线程<NSThread: 0x7fab9270b5f0>{number = 12, name = (null)}执行第11个任务

线程<NSThread: 0x7fab94b00260>{number = 4, name = (null)}执行第6个任务

线程<NSThread: 0x7fab94900400>{number = 2, name = (null)}执行第5个任务

线程<NSThread: 0x7fab9261c6e0>{number = 9, name = (null)}执行第1个任务

线程<NSThread: 0x7fab94a01cc0>{number = 7, name = (null)}执行第7个任务

线程<NSThread: 0x7fab92416080>{number = 14, name = (null)}执行第13个任务

线程<NSThread: 0x7fab92415ac0>{number = 5, name = (null)}执行第4个任务

线程<NSThread: 0x7fab92714400>{number = 13, name = (null)}执行第12个任务

线程<NSThread: 0x7fab92415ec0>{number = 10, name = (null)}执行第9个任务

线程<NSThread: 0x7fab926280b0>{number = 11, name = (null)}执行第10个任务

线程<NSThread: 0x7fab94900570>{number = 6, name = (null)}执行第14个任务

线程<NSThread: 0x7fab94900850>{number = 8, name = (null)}执行第15个任务

线程<NSThread: 0x7fab924160c0>{number = 15, name = (null)}执行第16个任务

线程<NSThread: 0x7fab926099e0>{number = 3, name = (null)}执行第17个任务

线程<NSThread: 0x7fab9270b5f0>{number = 12, name = (null)}执行第18个任务

线程<NSThread: 0x7fab94b00260>{number = 4, name = (null)}执行第20个任务

线程<NSThread: 0x7fab9241f490>{number = 16, name = (null)}执行第19个任务


      线程个数小于20。一般用来处理耗时操作

下面来看看其他函数

dispatch_after   延时操作,比如希望界面上某个弹框展示1s后消失。调用函数 dispatch_after(when: dispatch_time_t, queue: dispatch_queue_t, block: dispatch_block_t) 在指定的时间异步提交任务到制指定队列。
参数when 指定时间,传入DISPATCH_TIME_NOW,效果等同于dispatch_async;传入DISPATCH_TIME_FOREVER,任务将永不执行;所以一般传入dispatch_time(DISPATCH_TIME_NOW, Int64(t*Double(NSEC_PER_SEC))
t*Double(NSEC_PER_SEC)代表t秒。
参数queue可以是任意队列,一般传入主队列

dispatch_group

提交到queue中的任务都执行完后想做某种处理,就可调用该函数。例如异步下载小说A、B、C,下载完后提示用户,代码如下:

 let group =dispatch_group_create()

 let queue =dispatch_get_global_queue(0,0)

 dispatch_group_async(group, queue) { 

     print("下载小说A")

 }

 dispatch_group_async(group, queue) {

     print("下载小说B")

 }

 dispatch_group_async(group, queue) {

     print("下载小说C")

 }

 dispatch_group_notify(group,dispatch_get_main_queue()) {

     print("小说下载完成")

 }

  运行结果

下载小说C

下载小说A

下载小说B

小说下载完成


也可将dispatch_group_notify(group, dispatch_get_main_queue()) {  print("小说下载完成")换成

let flag = dispatch_group_wait(group, DISPATCH_TIME_FOREVER)

if flag == 0 {

   print("小说下载完成")

} else {

    print("小说下载超时")

}


DISPATCH_TIME_FOREVER 如果换成 dispatch_time(DISPATCH_TIME_NOW, Int64(t*Double(NSEC_PER_SEC)) 代表等待时长


dispatch_once  一次性操作  调用函数 dispatch_once(predicate: UnsafeMutablePointer<dispatch_once_t>, block: dispatch_block_t)

参数predicate  用来标识block是否执行的指针,必须是全局或静态变量,并且该指针所指向的区域的初始值为0,当任务执行完毕,系统会将其值置为-1


这个函数用于初始化全局数据,并且保证线程安全,常用于OC中的单例模式


dispatch_apply   结合dispatch_sync 与 dispatch_group 的函数  调用 dispatch_apply(iterations: Int, queue: dispatch_queue_t, block: (Int)->Void)


参数 iterations  将任务循环添加到指定队列的次数; block的参数是循环下标(从0开始)
调用该函数会将queue中的任务执行完才执行它后面的代码。queue如果是串行队列,queue中的任务会在当前线程中同步执行;queue如果是并行队列,当前线程会取出queue中的第一个任务执行,然后开新线程执行后面的任务,所开线程个数由系统决定。因为dispatch_apply具有dispatch_sync的特征,所以如果queue是串行队列并且与当前的队列是同一个对象时,就会出现线程死锁。

dispatch_apply主要用于异步并发处理数据,并且处理完后统一操作。所以dispatch_apply一般在子线程中调用。当然,像这样的操作用dispatch_group也可以实现。但是,思考这种情况,如果对象数组,里面所有元素需要并发执行某种操作,并且都执行完之后要统一做处理,这时如果用dispatch_group,则需要for in 遍历数组,用dispatch_apply则可以省去这种遍历。代码如下:

dispatch_async(queue) {

   dispatch_apply(arr.count,dispatch_get_global_queue(0,0)) { (i) in

       print(arr[i])

    }

    print("done")

}


dispatch_barrier_async


在处理数据读取时,为了避免数据竞争的问题,写入操作与写入操作或读取操作不能并发执行。针对这个例子,可以使用dispatch_barrier_async将写入操作提交到并发队列中,此时被提交的任务暂不执行,当他前面的任务执行完毕时在执行该任务,等到该任务执行完毕,后面提交的才执行执行。需要注意的是指定的queue应该是通过dispatch_queue_create创建的,系统定义的全局并发队列无效。示例代码如下:

let queue =dispatch_queue_create("queue",DISPATCH_QUEUE_CONCURRENT)

dispatch_async(queue) {print("读取操作1") }

dispatch_async(queue) {print("读取操作2") }

dispatch_async(queue) {print("读取操作3") }

dispatch_barrier_async(queue) {print("写入操作1") }

dispatch_async(queue) {print("读取操作4") }

dispatch_async(queue) {print("读取操作5") }

dispatch_async(queue) {print("读取操作6") }

dispatch_barrier_async(queue) {print("写入操作2") }

dispatch_async(queue) {print("读取操作7") }

dispatch_async(queue) {print("读取操作8") }


运行结果:

读取操作2

读取操作1

读取操作3

写入操作1

读取操作5

读取操作4

读取操作6

写入操作2

读取操作7

读取操作8


与之对应的是dispatch_barrier_sync,该函数会阻塞当前线程。它也会等待前面提交的任务执行完毕在执行该函数提交的任务,等它执行完毕后面的任务才提交到queue中执行


dispatch_semaphore  信号量。 简单来说就是控制访问资源的数量,比如系统有两个资源可以被利用,同时有三个线程要访问,只能允许两个线程访问,第三个应当等待资源被释放后再访问。
下面逐一介绍与之相关的三个函数:
(1)dispatch_semaphore_create(value: Int)   创建信号量,返回值类型dispatch_semaphore_t
参数value 为允许访问资源的线程数,该值必须 >= 0,否则会返回nil。当value为0时对访问资源的线程没有限制。
(2)dispatch_semaphore_signal(dsema:dispatch_semaphore_t)  信号量+1
(3)dispatch_semaphore_wait(dsema: dispatch_semaphore_t, timeout: dispatch_time_t)  该函数会让信号量-1

这个函数的具体作用是这样的,如果dsema信号量的值大于0,该函数所处线程就继续执行下面的语句,并且将信号量的值减1,返回值为0;如果desema的值为0,那么这个函数就阻塞当前线程等待timeout,如果等待的期间desema的值被dispatch_semaphore_signal函数加1了,且该函数所处线程获得了信号量,那么就继续向下执行并将信号量减1, 返回值为0。如果等待期间没有获取到信号量或者信号量的值一直为0,那么等到timeout时,其所处线程自动执行其后语句,返回值大于0。通过返回值是否为0,可以判断等待是否超时。

第二个函数和第一个函数是配套使用的,使用时先调用函数(3)将信号量-1,后调用函数(2)将信号量+1

为了加深理解,举一个在餐厅排队吃放的例子。假设某餐厅有50个座位,这相当于调用dispatch_semaphore_wait(value: Int)参数传入50,有顾客来吃饭相当于函数dispatch_semaphore_wait(dsema: dispatch_semaphore_t, timeout: dispatch_time_t),前50个顾客都有座位直接就餐。第51个顾客来到时就需要等待前面的顾客吃完饭离开,他才能就坐用餐。此时相当于dispatch_semaphore_wait函数阻塞当前线程。当有顾客用餐完毕离开时,此时就有了一个空位。相当于dispatch_semaphore_signal将型号来加1,第51个顾客等到座位,相当于dispatch_semaphore_wait返回值为0。

信号量是GCD同步的一种方式。前面介绍过dispatch_barrier_async是对queue中的任务进行批量同步处理,dispatch_sync是对queue中的任务单个同步处理,而dispatch_semaphore是对queue中的某个任务中的某部分(某段代码)同步处理。此时将dispatch_semaphore_wait中的参数传入1。

dispatch_semaphore 的使用如下:

let semaphore =dispatch_semaphore_create(1)

var arr = [Int]()

forin 0...100 {

    dispatch_async(dispatch_get_global_queue(0,0)) 

            /*

             其他并发操作

             */

            dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER)

            /*

             同步操作,例如 arr.append(i)

             */

            arr.append(i)

            dispatch_semaphore_signal(semaphore)

            /*

            其他并发操作

           */

     }

}


dispatch_suspend / dispatch_resume    暂停指定的队列 / 恢复指定的队列

有时候获取希望提交到queue中的任务暂不执行,等待某一时刻执行,这时候就可使用dispatch_suspend 和 dispatch_resume, 使用dispatch_suspend时对正在执行的任务不会起作用。

dispatch_suspend使指定队列的暂停计数+1,dispatch_resume使指定队列的暂停计数-1。使用它们时要注意几点:
(1)dispatch_suspend 与 dispatch_resume 成对出现 ,否则程序在运行时会crash
(2)dispatch_suspend 与 dispatch_resume 中指定的队列是通过 dispatch_queue_create 创建的,其他队列无效
  

dispatch_set_target_queue 

前面提到过,我们自己创建的队列的优先级是默认优先级,需要更改优先级需要调用dispatch_set_target_queue(系统的队列优先级不能修改)。实际上,dispatch_set_target_queue 主要有两个作用:

(1)更改 dispatch_queue_create 函数创建的队列的优先级。代码如下:

let queue =dispatch_queue_create("queue",DISPATCH_QUEUE_CONCURRENT)

dispatch_set_target_queue(queue,dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0))

函数中第一个是需要变更优先级的队列,第二个参数是需要传入全局并发队列,并且优先级是第一个参数想要的优先级。

(2)修改用户队列的目标队列,使多个serial queue在目标queue上一次只有一个执行。第二个参数是目标队列,第一个参数是任务将放在目标队列中的另一串行队列。

例如,用户要依次下载小说A、B、C,但下载任务放在不同的串行队列中,这时就可以依次调用dispatch_set_target_queue,将放有下载任务的队列作为第一个参数传入,让任务将目标队列中同步执行。 示例代码如下:

let targetQueue = dispatch_queue_create("targetQueue",DISPATCH_QUEUE_SERIAL)

        

let queue1 = dispatch_queue_create("queue1",DISPATCH_QUEUE_SERIAL)

let queue2 = dispatch_queue_create("queue2",DISPATCH_QUEUE_SERIAL)

let queue3 = dispatch_queue_create("queue3",DISPATCH_QUEUE_SERIAL)

        

dispatch_set_target_queue(queue1, targetQueue);

dispatch_set_target_queue(queue2, targetQueue);

dispatch_set_target_queue(queue3, targetQueue);

        

dispatch_async(queue1) { 

   print("开始下载小说A")

    /*

    下载中

     */

   print("小说A下载完成")

}

dispatch_async(queue1) {

   print("开始下载小说B")

   /*

    下载中

    */

   print("小说B下载完成")

}

dispatch_async(queue1) {

   print("开始下载小说C")

   /*

    下载中

    */

   print("小说C下载完成")

}

运行结果:

开始下载小说A

小说A下载完成

开始下载小说B

小说B下载完成

开始下载小说C

小说C下载完成








  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值