关于 YYAsyncLayer 可能出现的问题
问题提出
在看@indulge_in的YYAsyncLayer 源码剖析:异步绘制时看到:
要点 3 :轮询返回队列
使用原子自增函数
OSAtomicIncrement32()
对局部静态变量counter
进行自增,然后通过取模运算轮询返回队列。注意这里使用了一个判断:
if (cur < 0) cur = -cur
,当cur
自增越界时就会变为负数最大值(在二进制层面,是用正整数的反码加一来表示其负数的)。
if (cur < 0) cur = -cur
开始是觉得很巧妙,使用OSAtomicIncrement32()
自增来均匀地取队列数组里面的队列,但是仔细一想,发现了一个问题。
cur
的数据类型是int32_t
,所以当自增到越界的时候就会变为负数的最大值,而我们知道,正数的最大值为2147483647
,而负数的最大值为-2147483648
,绝对值不一样是因为正数有0。
所以当cur
自增越界时就会变为负数最大值-2147483648
,而这时候直接通过if (cur < 0) cur = -cur;
这个判断来取正肯定是有问题的,因为int32_t
类型不存在2147483648
。
问题验证
而cur
值到底会变成什么值呢,这我就写了一个验证代码:
static int counter = INT32_MAX;
int cur = OSAtomicIncrement32(&counter);
if (cur < 0) {
cur = -cur;
}
NSLog(@" %d", cur);
结果打印输出是还是-2147483648
,我推测是因为无法置为负值,所以不变。如果有更好的答案,可以评论一下,谢谢。
再看源码:
if (cur < 0) cur = -cur;
return queues[(cur) % queueCount];
而cur
取正失败则会导致取余以后的数的值也可能出现负值,因为-2147483648
是2的31次方,如果queueCount
不是2的倍数,则取余的值就不是0,而出现负值。
而根据源码:
queueCount = (int)[NSProcessInfo processInfo].activeProcessorCount;
queueCount = queueCount < 1 ? 1 : queueCount > MAX_QUEUE_COUNT ? MAX_QUEUE_COUNT : queueCount;
而经过查阅,A11 和 A12 芯片 CPU 都是6核心并且能所有核心同时工作,所以有风险会出现余数为负值。
下面通过简单改写源码验证负值情况:
static dispatch_queue_t YYAsyncLayerGetDisplayQueue() {
#define MAX_QUEUE_COUNT 16
static int queueCount = 6;
static dispatch_queue_t queues[MAX_QUEUE_COUNT];
static dispatch_once_t onceToken;
static int32_t counter = INT32_MAX - 5;
dispatch_once(&onceToken, ^{
for (NSUInteger i = 0; i < queueCount; i++) {
dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INITIATED, 0);
queues[i] = dispatch_queue_create("com.ibireme.yykit.render", attr);
}
});
int32_t cur = OSAtomicIncrement32(&counter);
if (cur < 0) cur = -cur;
int remainder = cur%queueCount;
NSLog(@" %d", remainder);
return queues[remainder];
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
for (NSInteger i = 0; i < 10; i++) {
dispatch_queue_t queue = YYAsyncLayerGetDisplayQueue();
dispatch_async(queue, ^{
NSLog(@"");
});
}
}
return 0;
}
这段代码的结果是,当cur
自增越界为-2147483648
时,取余后的余数为-2
,而此时调用YYAsyncLayerGetDisplayQueue
返回queue
时出现EXC_BAD_ACCESS
崩溃。
问题解决
下面来讲一下如何解决这个问题。在发现这个问题之后我就去找了 YYDispatchQueuePool 库看看是否有同样的问题,因为YYAsyncLayer
也是在本地没有YYDispatchQueuePool
的情况下才使用这段代码。结果是并没有这个问题,先看代码:
static dispatch_queue_t YYDispatchContextGetQueue(YYDispatchContext *context) {
uint32_t counter = (uint32_t)OSAtomicIncrement32(&context->counter);
void *queue = context->queues[counter % context->queueCount];
return (__bridge dispatch_queue_t)(queue);
}
YYDispatchQueuePool
中OSAtomicIncrement32()
自增调用后会强制转换为uint32_t
所有就完全不会出现负值的情况,就算自增越界后也会变为0。所以counter
不会出现负值,余数也不会出现负值,就完全没有以上的问题了。所以,只需要参照YYDispatchQueuePool
将代码:
int32_t cur = OSAtomicIncrement32(&counter);
if (cur < 0) cur = -cur;
改为
uint32_t cur = (uint32_t)OSAtomicIncrement32(&counter);
就可以解决此问题。
已经提交 PullRequest https://github.com/ibireme/YYAsyncLayer/pull/21