美文网首页面试题目
iOS-多线程-总结

iOS-多线程-总结

作者: 笑破天 | 来源:发表于2022-07-01 16:01 被阅读0次

    容易混淆的概念(队列、线程、串行并行、同步异步、任务)

    任务:代码块(一行或多行),也叫block
    队列:管理任务的一个线性数据结构,有两种:串行和并行。区别是是否有开启新线程的能力。
    同步异步:说的是任务的执行方式。同步指的是阻塞当前线程等待该任务执行完才继续下面的任务。

    // dispatch_sync官方原文
    This function submits a block to the specified dispatch queue for synchronous execution. Unlike dispatch_async, this function does not return until the block has finished. Calling this function and targeting the current queue results in deadlock.
    Unlike with dispatch_async, no retain is performed on the target queue. Because calls to this function are synchronous, it "borrows" the reference of the caller. Moreover, no Block_copy is performed on the block.
    As a performance optimization, this function executes blocks on the current thread whenever possible, with one exception: Blocks submitted to the main dispatch queue always run on the main thread.
    //大意:
    1、是在当前线程上指定队列中同步地执行任务。会阻塞当前线程等待该任务执行完才继续下面的任务,主队列除外。
    2、是立即返回无需等待的,所以不需要block copy操作
    

    iOS多线程方案有四种:pthread(C),NSThread(OC), GCD(C,自动管理),NSOperation(GCD的封装)。常用的是自动管理线程生命周期的GCD和NSOperation,其中GCD有以灵活性居先。

    一、GCD

    1、四种基本组合

    串行+同步

        NSLog(@"1");
        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"2");
        });
        NSLog(@"3");
    // 打印1,崩溃在__DISPATCH_WAIT_FOR_QUEUE__,死锁
    

    串行+异步

        dispatch_queue_t queue = dispatch_get_main_queue();
        NSLog(@"1");
        dispatch_async(queue, ^{
            NSLog(@"2-1-%@", [NSThread currentThread]);
        });
        dispatch_async(queue, ^{
            NSLog(@"2-2-%@", [NSThread currentThread]);
        });
        NSLog(@"3");
    // 打印1,3,2-1-main,2-2-main
    

    并行+同步

        dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
        NSLog(@"1");
        dispatch_sync(queue, ^{
            [NSThread sleepForTimeInterval:1];
            NSLog(@"2-%@", [NSThread currentThread]);
        });
        NSLog(@"3");
    // 打印1,2-main,3
    

    并行+异步

        dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
        NSLog(@"1");
        dispatch_async(queue, ^{
            NSLog(@"2-1-%@", [NSThread currentThread]);
        });
        dispatch_async(queue, ^{
            NSLog(@"2-2-%@", [NSThread currentThread]);
        });
        NSLog(@"3");
    // 打印1,3,2-1-num7,2-2-num4,开启了新线程
    

    总结:串行+同步会死锁。并行+异步可开启新线程。

    2、复杂情况

    不同队列嵌套

        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"1-%@", [NSThread currentThread]);
            dispatch_sync(dispatch_get_main_queue(), ^{
                NSLog(@"2-%@", [NSThread currentThread]);
            });
            NSLog(@"3-%@", [NSThread currentThread]);
        });
        NSLog(@"4-%@", [NSThread currentThread]);
    // 打印4-main,1-num5,2-mian,3-num5。1/4顺序不定
    

    相同队列嵌套

    3、api使用

    1.dispatch_after

    // 适用于不太精准的延迟执行
        NSLog(@"start");
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"end");
        });
    

    dispatch_after不像NSTimer和performSelector:afterDelay一样需要runloop支持,但dispatch_after 并不是在指定时间后执行任务,而是在指定时间之后才将任务提交到队列中,这个延迟的时间是不精确的。

    1. dispatch_once
    static TheClass *instance = nil;
    + (instance)sharedInstance {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            instance = [[TheClass alloc] init];
        });
        return instance;
    }
    
    1. dispatch_apply
      同步执行按照指定的次数提交到指定的队列中的任务,可以将迭代任务转换为并发任务,迭代任务繁重时用它效率成倍提升。
    dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t iteration) {
            NSLog(@"%zu-%@", iteration, [NSThread currentThread]);
        });
    // 可创建多个线程
    
    1. dispatch_barrier_async
      提供一个 “栅栏” 将两组异步执行的任务分隔开,保证先于栅栏方法提交到队列的任务全部执行完成之后,然后开始执行将栅栏任务,等到栅栏任务执行完成后,该队列便恢复原本执行状态。只适用于自定义并发队列,全局并发队列和串行队列无意义。
        dispatch_queue_t queue = dispatch_queue_create("thread", DISPATCH_QUEUE_CONCURRENT);
        dispatch_async(queue, ^{
            NSLog(@"test1");
        });
        dispatch_async(queue, ^{
            NSLog(@"test2");
        });
        dispatch_async(queue, ^{
            NSLog(@"test3");
        });
        
        dispatch_barrier_sync(queue, ^{
            [NSThread sleepForTimeInterval:1];
            NSLog(@"barrier");
        });
        NSLog(@"aaa");
        dispatch_async(queue, ^{
            NSLog(@"test4");
        });
        NSLog(@"bbb");
        dispatch_async(queue, ^{
            NSLog(@"test5");
        });
        dispatch_async(queue, ^{
            NSLog(@"test6");
        });
    //打印 test1,test3,test2,barrier,aaa,bbb,test4,test5,test6
    //异步 aaa,test1,bbb,test2,test3,barrier,test4,test5,test6
    // 执行的顺序,同一线程内同步有序,异步无序和执行任务效率有关。不同线程只和任务效率有关。
    

    5.dispatch_group

        dispatch_group_t group = dispatch_group_create();
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        NSLog(@"1");
    //    dispatch_group_enter(group);
        dispatch_group_async(group, queue, ^{
            dispatch_async(queue, ^{
                sleep(1); //这里线程睡眠1秒钟,模拟异步请求
                NSLog(@"2");
    //            dispatch_group_leave(group);
            });
        });
        dispatch_group_notify(group, queue, ^{
            NSLog(@"3");
        });
    //    dispatch_group_wait(group, DISPATCH_TIME_NOW);
        NSLog(@"4");
        dispatch_async(queue, ^{
            NSLog(@"5");
        });
    }
    // 打印1,4,3,5,2(3,5顺序不定)。
    //dispatch_group_enter/leave: notify只负责group里面的任务执行完,如果group里面嵌套有异步任务,是不会管他就直接返回的。这时就需要enter/leave了,成对出现的,enter多了会阻塞,leave多了会崩溃。
    // dispatch_group_wait:阻塞group到某个时刻,然后释放继续执行下面的任务,参数DISPATCH_TIME_NOW表示不阻塞,相当于没有,参数DISPATCH_TIME_FOREVER表示永远阻塞,还可以自定义为3秒后的时刻,表示阻塞3秒,之后放开。打开wait并设置time为FOREVER后,打印为1,4,3,5,2,3在5前面
    

    6、dispatch_semaphore
    GCD的锁机制,iOS线程同步除了os_unfair_lock 和
    OSSpinLock之外,dispatch_semaphore的性能是很好的极力推荐使用。
    dispatch_semaphore_create:根据传入的初始值创建一个信号量。
    不可传入负值。运行过程中,若内部值为负数,则这个值的绝对值便是正在等待资源的线程数。
    dispatch_semaphore_wait:信号量 -1。 -1 之后的结果值小于 0 时,线程阻塞,并以 FIFO 的方式等待资源。
    dispatch_semaphore_signal:信号量 +1。 +1 之后的结果值大于 0 时,以 FIFO 的方式唤醒等待的线程。
    一般用来:控制最大并发数、数组多线程安全、阻塞异步任务

    // 控制最大并发数
        dispatch_semaphore_t semaphore = dispatch_semaphore_create(3);
        dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
        for (int i = 0; i < 10; i++) {
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            dispatch_async(queue, ^{
                NSLog(@"%d-%@", i, [NSThread currentThread]);
                dispatch_semaphore_signal(semaphore);
            });
        }
    
    // YYKit 中 YYThreadSafeArray 的实现(无法多读)
    // 通过宏定义对代码块进行加锁操作
    #define LOCK(...) dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER); \
    __VA_ARGS__; \
    dispatch_semaphore_signal(_lock);
    // id obj = array[idx];
    - (id)objectAtIndexedSubscript:(NSUInteger)idx {
        LOCK(id o = [_arr objectAtIndexedSubscript:idx]); return o;
    }
    // array[idx] = obj;
    - (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)idx {
        LOCK([_arr setObject:obj atIndexedSubscript:idx]);
    }
    

    7、dispatch_source
    用GCD的函数指定一个希望监听的系统事件类型,再指定一个捕获到事件后进行逻辑处理的闭包或者函数作为回调函数,然后再指定一个该回调函数执行的Dispatch Queue即可,当监听到指定的系统事件发生时会调用回调函数,将该回调函数作为一个任务放入指定的队列中执行。
    https://blog.csdn.net/hailideboke/article/details/78711514

    4、灵活运用

    1.阻塞异步任务的几种方式
    group、barrier、semaphore

    二、NSOperation

    三、其他相关问题

    1、线程与runloop
    延时执行任务:
    dispatch_after 比 NSTimer 优秀,因为他不需要指定 Runloop 的运行模式。 dispatch_after 比 performSelector:afterDelay: 优秀,因为它不需要 Runloop 支持。

    iOS定时器:
    NSTimer:
    dispatch_after:

    2、线程与数据安全
    多读单写-barrier、单读单写-semaphore

    3、如何取消GCD任务
    dispatch_block_cancel、外部变量控制

    相关文章

      网友评论

        本文标题:iOS-多线程-总结

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