Swift3.0 gcd学习(2)
上一篇简单梳理了下gcd的基础概念和一些基本的使用方法。这一篇希望再深入研究下gcd的一些玩法,主要介绍在gcd里,怎样保证线程同步,有错误希望大家指正。
demo git地址
调度屏障barrier
有时会在一个并发的队列里读写一个数据对象,但如果对象并非线程安全,就会出现资源抢占的问题。之前使用dispatch_barrier_async来解决这个问题,在swift3.0,被搬到了DispatchWorkItem的flags属性中:
let workItemA = DispatchWorkItem(qos: DispatchQoS.default, flags: DispatchWorkItemFlags.barrier)
{
for i in 0...5
{
print("barrier workItem block: ", i);
}
}
let workItemB = DispatchWorkItem()
{
for i in 0...5
{
print("workItem block: ", i);
}
}
let u = DispatchQueue(label: "com.justin.barrierAsync", qos: DispatchQoS.default, attributes: DispatchQueue.Attributes.concurrent, autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency.workItem, target: nil);
u.async {
for i in 0...5
{
print("block 1: ", i);
}
}
u.async(execute: workItemA);
u.async {
for i in 0...5
{
print("block 2: ", i);
}
}
workItemB只做最普通的并发操作,输出结果如下:
block 1: 0
workItem block: 0
block 2: 0
block 1: 1
workItem block: 1
block 2: 1
block 1: 2
workItem block: 2
block 2: 2
block 1: 3
workItem block: 3
block 2: 3
block 1: 4
workItem block: 4
block 2: 4
block 1: 5
workItem block: 5
block 2: 5
可以看出,三个block交错运行
下面我们换成workItemA,注意这个workItem的flag设置成了DispatchWorkItemFlags.barrier
block 1: 0
block 1: 1
block 1: 2
block 1: 3
block 1: 4
block 1: 5
barrier workItem block: 0
barrier workItem block: 1
barrier workItem block: 2
barrier workItem block: 3
barrier workItem block: 4
barrier workItem block: 5
block 2: 0
block 2: 1
block 2: 2
block 2: 3
block 2: 4
block 2: 5
可以看出barrier确保提交的block是指定队列中,在特定时段唯一在执行的一个。只有在所有先于barrier的block都完成的情况下barrier block才开始执行,并且确保队列在此过程不会执行其它block。闭包完成后队列恢复。需要注意barrier只在自己创建的队列上有这种作用。
信号
另一种解决资源抢占问题的方法,就是使用信号。简单来说信号就是控制访问资源的数量,假设系统有10个资源,每个线程进入执行代码时占用一个资源,执行完成后,释放资源。当这10个线程的资源都没被释放时,第11个线程想要进入,就会被挡在外面。
声明一个信号:
let s = DispatchSemaphore(value: 2);
降低一个信号量:
s.wait();
增加一个信号量:
s.signal();
当信号量为0时,进程就会被阻塞,可以理解为资源被占完,其它线程想要入场,只能等待。一言不合上代码:
let s = DispatchSemaphore(value: 2);
let g = DispatchQueue.global();
g.async {
s.wait();
for i in 0...5
{
print("block 1: ", i);
}
s.signal();
}
g.async {
s.wait();
for i in 0...5
{
print("block 2: ", i);
}
s.signal();
}
g.async {
s.wait();
for i in 0...5
{
print("block 3: ", i);
}
s.signal();
}
//输出结果
block 2: 0
block 1: 0
block 2: 1
block 1: 1
block 2: 2
block 1: 2
block 2: 3
block 1: 3
block 2: 4
block 1: 4
block 2: 5
block 1: 5
block 3: 0
block 3: 1
block 3: 2
block 3: 3
block 3: 4
block 3: 5
可以看出,因为信号对象声明时,只设置了2个信号量,所以当block1和block2进入执行代码后,block3就被挡在了外面,直到前面两个block把信号量释放出来后, block3才开始执行。
所以,在一些并发队列里处理一些非线程安全的数据时,可以这么干:
let g = DispatchQueue.global();
let s = DispatchSemaphore(value: 1);
var arr:[Int] = [];
for i in 0...100
{
g.async {
s.wait();//#1
arr.append(i);
s.signal();//#2
}
}
如果你把上面代码#1,#2注释掉,就会出现:
fatal error: UnsafeMutablePointer.deinitialize with negative count
fatal error: UnsafeMutablePointer.deinitialize with negative count