线程相关
异步任务&同步任务
两者的主要区别是:是否等待队列的任务执行结束,以及是否具备开启新线程的能力。
- 同步执行(sync)
- 同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后再继续执行。
- 只能在当前线程中执行任务,不具备开启新线程的能力。
- 异步执行(async)
- 异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务。
- 可以在新的线程中执行任务,具备开启新线程的能力。
|
|
队列(Dispatch Queue)
队列是一种特殊的线性表,采用 FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务。
- 串行队列(Serial Dispatch Queue)
- 每次只有一个任务被执行。让任务一个接着一个地执行。(只开启一个线程,一个任务执行完毕后,再执行下一个任务)
DISPATCH_QUEUE_SERIAL
表示串行队列- dispatch_get_main_queue() 属于串行队列
- 并发队列(Concurrent Dispatch Queue)
- 可以让多个任务并发(同时)执行。(可以开启多个线程,并且同时执行任务)
DISPATCH_QUEUE_CONCURRENT
表示并发队列。- dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);属于并发队列。第一个参数表示队列优先级,一般用
DISPATCH_QUEUE_PRIORITY_DEFAULT
。第二个参数暂时没用,用0
即可
|
|
区别 | 并发队列 | 串行队列 | 主队列 |
---|---|---|---|
同步(sync) | 没有开启新线程,串行执行任务 | 没有开启新线程,串行执行任务 | 死锁卡住不执行 |
异步(async) | 有开启新线程,并发执行任务 | 有开启新线程(1条),串行执行任务 | 没有开启新线程,串行执行任务 |
区别 | 『异步执行+并发队列』嵌套『同一个并发队列』 | 『同步执行+并发队列』嵌套『同一个并发队列』 | 『异步执行+串行队列』嵌套『同一个串行队列』 | 『同步执行+串行队列』嵌套『同一个串行队列』 |
---|---|---|---|---|
同步(sync) | 没有开启新的线程,串行执行任务 | 没有开启新线程,串行执行任务 | 死锁卡住不执行 | 死锁卡住不执行 |
异步(async) | 有开启新线程,并发执行任务 | 有开启新线程,并发执行任务 | 有开启新线程(1 条),串行执行任务 | 有开启新线程(1 条),串行执行任务 |
开辟线程的个数和异步+并发队列有关。
队列是依托于线程而存在的,只有一个线程串行队列并行队列并没有什么不同。所以可以先分析线程的个数再分析队列从而得到执行的步骤。
GCD 延时执行方法:dispatch_after
我们经常会遇到这样的需求:在指定时间(例如 3 秒)之后执行某个任务。可以用 GCD 的dispatch_after
方法来实现。
需要注意的是:dispatch_after
方法并不是在指定时间之后才开始执行处理,而是在指定时间之后将任务追加到主队列中。严格来说,这个时间并不是绝对准确的,但想要大致延迟执行任务,dispatch_after
方法是很有效的
|
|
GCD 栅栏方法:dispatch_barrier_async
dispatch_barrier_async
方法会等待前边追加到并发队列中的任务全部执行完毕之后,再将指定的任务追加到该异步队列中。然后在 dispatch_barrier_async
方法追加的任务执行完毕之后,异步队列才恢复为一般动作,接着追加任务到该异步队列并开始执行。
我们要注意的是不能使用全局并发队列( dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);)否则会散失栅栏函数的意义
|
|
实现同步锁
|
|
GCD 一次性代码(只执行一次):dispatch_once
整个程序运行过程中只执行一次的代码时,我们就用到了 GCD 的 dispatch_once
方法。使用 dispatch_once
方法能保证某段代码在程序运行过程中只被执行 1 次,并且即使在多线程的环境下,dispatch_once
也可以保证线程安全。
|
|
GCD 快速迭代方法:dispatch_apply
dispatch_apply
按照指定的次数将指定的任务追加到指定的队列中,并等待全部队列执行结束。
如果是在串行队列中使用 dispatch_apply
,那么就和 for 循环一样,按顺序同步执行。但是这样就体现不出快速迭代的意义了。
我们可以利用并发队列进行异步执行。比如说遍历 0~5 这 6 个数字,for 循环的做法是每次取出一个元素,逐个遍历。dispatch_apply
可以 在多个线程中同时(异步)遍历多个数字。
还有一点,无论是在串行队列,还是并发队列中,dispatch_apply 都会等待全部任务执行完毕,这点就像是同步操作,也像是队列组中的 dispatch_group_wait
方法。
|
|
GCD队列组:dispatch_group
调用队列组的 dispatch_group_async
先把任务放到队列中,然后将队列放入队列组中异步执行。如不调用队列组则是顺序执行。或者使用队列组的 dispatch_group_enter
、dispatch_group_leave
组合来实现 dispatch_group_async
。
调用队列组的 dispatch_group_notify
回到指定线程执行任务。或者使用 dispatch_group_wait
回到当前线程继续向下执行(会阻塞当前线程)
dispatch_group_enter
标志着一个任务追加到 group,执行一次,相当于 group 中未执行完毕任务数 +1
dispatch_group_leave
标志着一个任务离开了 group,执行一次,相当于 group 中未执行完毕任务数 -1。
当 group 中未执行完毕任务数为0的时候,才会使 dispatch_group_wait
解除阻塞,以及执行追加到 dispatch_group_notify
中的任务。
|
|
GCD 信号量:dispatch_semaphore
信号量是基于计数器的一种多线程同步机制,用来管理对资源的并发访问。(类似于NSOperationQueue的设置最大并发量)
在 Dispatch Semaphore 中,使用计数来完成这个功能,计数小于 0 时等待,阻塞线程。计数为 0 或大于 0 时,计数减 1 ,继续运行。
dispatch_semaphore_create
:创建一个 Semaphore 并初始化信号的总量
dispatch_semaphore_signal
:发送一个信号,让信号总量加 1
dispatch_semaphore_wait
:可以使总信号量减 1,信号总量小于 0 时就会一直等待(阻塞所在线程),否则就可以正常执行。
实现线程同步,将异步执行任务转换为同步执行任务。
|
|
执行顺如下:
- semaphore 初始创建时计数为 0。
异步执行
将任务 1
追加到队列之后,不做等待,接着执行dispatch_semaphore_wait
方法,semaphore 减 1,此时semaphore == -1
,当前线程进入等待状态。- 然后,异步任务 1 开始执行。任务 1 执行到
dispatch_semaphore_signal
之后,总信号量加 1,此时semaphore == 0
,正在被阻塞的线程(主线程)恢复继续执行。 - 最后打印
semaphore---end,number = 100
。
这样就实现了线程同步,将异步执行任务转换为同步执行任务。