iOS多线程使用踩过的坑

作者: zaijianbali | 来源:发表于2017-06-09 13:07 被阅读84次

    iOS多线程使用踩过的坑

    iOS 开发过程中,我们经常使用系统提供的方法使用多线程(全局并发)
    包括:

     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            });      
    

    使用起来很方便,包括如下几类

    • DISPATCH_QUEUE_PRIORITY_HIGH 2
    • DISPATCH_QUEUE_PRIORITY_DEFAULT 0
    • DISPATCH_QUEUE_PRIORITY_LOW (-2)
    • DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN

    我们最常用的就是DISPATCH_QUEUE_PRIORITY_DEFAULT队列,

    理论上系统系统提供的方法没有问题,好多文献也说,并发队列可以根据实际情况来自动产生合理的线程数。多少算合理呢?

    按照我的理解,3到5个的并发还算比较合理。

    我曾遇到一个DB相关问题,开启了80多个异步线程,这个就不合理。
    由于场景复杂,不好描述,我举个类似的例子。

    - (void)dispatchTest1
    {
        for (NSInteger i = 0; i< 10000 ; i++) {
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                [self dispatchTask:i];
            });
        }
    }
    
    - (void)dispatchTask:(NSInteger)index
    {
            //模拟耗时操作,比如DB,网络,文件读写等等
            sleep(30);
            NSLog(@"----:%ld",index);
    }
    

    为了复现场景,我举得例子比较极端,写了10000 的循环,针对IM读写场景这种现象是存在的,频繁的读写DB

    通过断点我们看到开启了20多个异步线程。

    场景如图:

    线程图

    原因是什么呢?由于我们执行的耗时操作,也就是sleep(30),而dispatchTest1 方法所在的线程为同步执行,但开启的DISPATCH_QUEUE_PRIORITY_DEFAULT 异步线程耗时,也就是说短时间内执行不完成。

    系统提供的全局并发控制开启的线程,一定有上限,不然达不到管理的完善性,很有可能上在这种情况下已经达到上限。而后续的线程的执行,必须依赖于前面线程执行的结束,如果前面一直不结束,那么后面一直得到不到执行。笔者遇到的DB问题曾经打到了80多个异步线程等待执行。最终出现了所谓的“冻屏”。

    我们都知道,
    主线程占用内存:1M, 子线程占用内存:512k
    如果开启上百个,也是一笔不小的开销
    其次,这么多的线程抢占cpu,势必会导致主线程卡顿,比如现象是冻屏(任何操作无效,感觉和一张图片一样)
    更深入的说:如果出现“冻屏”这种问题,其问题比crash更严重,crash可能是偶发,这个如果是必现的话,影响的不是这个功能,而是全部。用户如何想?是不是有想卸载的冲动。一旦卸载再装的可能性比较小,所以一定不要出现这种问题。

    冻屏不可怕,可怕的是冻屏了找不到原因和解决方案。

    解决方案1:异步串行队列执行

    - (void)dispatchTest2
    {
        dispatch_queue_t queue =  dispatch_queue_create("com.zw", DISPATCH_QUEUE_SERIAL);
        for (NSInteger i = 0; i< 10000 ; i++) {
            dispatch_async(queue, ^{
                [self dispatchTask:i];
            });
        }
    }
    
    - (void)dispatchTask:(NSInteger)index
    {
            sleep(30);
            NSLog(@"----:%ld",index);
    }
    

    效果如下:


    并发串行队列

    这样子虽然解决并发问题,但只是一种伪策略,也就是把原来在主线程执行的串行耗时操作放到了另外一个串行队列里,根本还是一个串行队列,只解决了主线程和这个线程的并发,开启的并发并没有什么卵用。

    我们想要的效果是主线程和任务线程之间要并行,任务和任务线程也要并行

    解决办法是,限制 异步执行线程的个数,通过组管理和信号量机制,

    - (void)dispatchTest3
    {
        dispatch_group_t group = dispatch_group_create();
        dispatch_semaphore_t semaphore = dispatch_semaphore_create(4);
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
        for (NSInteger i = 0; i< 10000 ; i++) {
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            dispatch_group_async(group, queue, ^{
                [self dispatchTask:i];
                dispatch_semaphore_signal(semaphore);
            });
        }
    }
    
    - (void)dispatchTask:(NSInteger)index
    {
            sleep(30);
            NSLog(@"----:%ld",index);
    }
    

    这样开启的线程个数也就是4个

    结果如图,


    异步并行队列

    那么试问:系统的提供的多线程真的安全吗?

    答案是否定的。尤其针对耗时操作

    文件读写DB操作等。

    解决方案就是,要么串行执行,要么限制并发线程的数量。

    本文解释权归:子文

    如需转载请注明出处,谢谢

    来杯可乐催更吧

    请子文喝可乐

    相关文章

      网友评论

      • 谢谢生活:解决方案1如果用并发队列开行吗,那样需要管理线程数吗?

      本文标题:iOS多线程使用踩过的坑

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