栅栏函数的拓展和底层源码分析
栅栏函数基础
同步栅栏函数/异步栅栏函数
GCD中常用的栅栏函数,主要有两种
- 同步栅栏函数dispatch_barrier_sync(在主线程中执行):
前面的任务执行完毕才会来到这里,但是同步栅栏函数会堵塞线程,影响后面的任务执行
- 异步栅栏函数dispatch_barrier_async:
前面的任务执行完毕才会来到这里
栅栏函数最直接的作用就是控制任务执行顺序,使同步执行。
栅栏函数栏队列
- (void)demo2 {
dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue2, ^{
sleep(2);
NSLog(@"异步任务");
});
dispatch_barrier_async(queue2, ^{
NSLog(@"栅栏任务");
});
NSLog(@"主线程任务");
}
打印结果
主线程任务
异步任务
栅栏任务
异步栅栏函数阻塞的是队列,而且必须是自定义的并发队列,不影响主线程任务的执行
栅栏函数的同步/异步栏线程
- (void)demo2 {
dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue2, ^{
sleep(2);
NSLog(@"异步任务");
});
dispatch_barrier_sync(queue2, ^{
NSLog(@"栅栏任务");
});
NSLog(@"主线程任务");
}
打印结果
异步任务
栅栏任务
主线程任务
dispatch_barrier_sync 它是同步,所以必须dispatch_barrier_sync^{ () }block 里面执行完,才执行后面
同步栅栏函数阻塞的是线程,且是主线程,会影响主线程其他任务的执行
栅栏函数必须是自定义并发队列
栅栏用于数据安全
崩溃代码
- (void)demo2 {
dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);
NSMutableArray *mArray = [NSMutableArray array];
for (NSInteger i = 0; i < 5000; i++) {
dispatch_async(queue2, ^{
NSString *url = [NSString stringWithFormat:@"%ld.png",(long)i];
[mArray addObject:url];
});
}
}
运行崩溃
崩溃分析
崩溃的堆栈中看到
崩溃之前调用了-[__NSArrayM insertObject:atIndex:]这个函数,我们再objc底层源码中去查看如下:
insertObject:底层源码
- (id)insertObject:anObject at:(unsigned)index
{
register id *this, *last, *prev;
if (! anObject) return nil;
if (index > numElements)
return nil;
if ((numElements + 1) > maxElements) {
volatile id *tempDataPtr;
/* we double the capacity, also a good size for malloc */
// 这里在数组超过一定的空间之后就进行了双倍的扩容
maxElements += maxElements + 1;
// 这里数组tempDataPtr 进行了realloc操作 所以在多个线程同时访问的时候就会出现问题
tempDataPtr = (id *) realloc (dataPtr, DATASIZE(maxElements));
dataPtr = (id*)tempDataPtr;
}
this = dataPtr + numElements;
prev = this - 1;
last = dataPtr + index;
while (this > last)
*this-- = *prev--;
*last = anObject;
numElements++;
return self;
}
- (id)addObject:anObject
{
return [self insertObject:anObject at:numElements];
}
这段就是可变数组添加数据时候底层实现,可以很清晰的看到,当数组的容量超过一定的maxElements的时候就会maxElements += maxElements + 1;,并且进行realloc重新创建了一个新的数组的操作,
在多线程的操作,如果数组添加的元素太多就会出现给旧数组添加元素的时候,旧的数组其实已经被替代的情况,这样就出现了崩溃。
数组元素比较小
可以看到并不会崩溃!!!
崩溃总结
异步并发执行addObject的时候会造成数组指针赋值错误的崩溃情况,当数组的容量maxElements固定之后就不会重新realloc ,就避免了同时访问数组失败的问题,
栅栏函dispatch_barrier_async使线程安全
互斥锁@synchronized 也是可以的
- (void)demo2 {
dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);
NSMutableArray *mArray = [NSMutableArray array];
for (NSInteger i = 0; i < 10000; i++) {
dispatch_async(queue2, ^{
NSString *url = [NSString stringWithFormat:@"%ld.png",(long)i];
dispatch_barrier_sync(queue2, ^{
[mArray addObject:url];
});
});
}
}
栅栏函数用全局队列还是崩溃
- 如果栅栏函数中使用 全局队列, 运行会崩溃,原因是系统也在用全局并发队列,使用栅栏同时会拦截系统的,所以会崩溃
- 如果将自定义并发队列改为串行队列,即serial ,串行队列本身就是有序同步 此时加栅栏,会浪费性能
- 栅栏函数只会阻塞一次
dispatch_barrier_async源码
#ifdef __BLOCKS__
void
dispatch_barrier_async(dispatch_queue_t dq, dispatch_block_t work)
{
dispatch_continuation_t dc = _dispatch_continuation_alloc();
uintptr_t dc_flags = DC_FLAG_CONSUME | DC_FLAG_BARRIER;
dispatch_qos_t qos;
qos = _dispatch_continuation_init(dc, dq