美文网首页
关于 YYAsyncLayer 可能出现的问题

关于 YYAsyncLayer 可能出现的问题

作者: pepsikirk | 来源:发表于2018-11-07 20:24 被阅读467次

    先上YYAsyncLayer 源码链接

    问题提出

    在看@indulge_inYYAsyncLayer 源码剖析:异步绘制时看到:

    要点 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);
    }
    

    YYDispatchQueuePoolOSAtomicIncrement32()自增调用后会强制转换为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

    相关文章

      网友评论

          本文标题:关于 YYAsyncLayer 可能出现的问题

          本文链接:https://www.haomeiwen.com/subject/btuvxqtx.html