多线程

150 阅读6分钟

GCD

  • 同步都是提交到当前线程执行。并且同步添加的任务必须马上执行。

  • 串行队列是先进先出。主队列是串行队列。主队列提交到主线程。

  • 异步才有开启线程的能力、但是异步提交到主队列会的任务提交到主线程上,不会开启新线程.

  • 如果当前队列是串行队列,通过同步函数向当前队列中添加任务会造成死锁

  • 主队列由系统自动创建,并与应用程序的主线程相关联。主队列上的任务一定是在主线程上执行(不过主线程并不是只执行主队列的任务,为了避免线程切换对性能的消耗,主线程还有可能会执行其他队列的任务)。主队列是一个串行队列,所以即便是在异步函数中也不会去开启新的线程。它只有在主线程空闲的时候才能调度里面的任务。

  • GCD开启的线程默认没有runloop,performSelector:withObject:afterDelay 需要提交到runloop上。所以在async开启的新线程中performSelector:withObject:afterDelay不会执行

  • 使用sync函数往当前串行队列中添加任务,会卡住当前的串行队列(产生死锁)

  • 使用sync函数往当前 串行队列中添加任务,会造成死锁

同步异步
主线队列无新线程,死锁无新线程,串行执行
全局队列无新线程,串行执行有新线程,并发执行
自创串行队列无新线程,串行执行有新线程,串行执行
自创并发队列无新线程,串行执行有新线程,并发执行
- (void)interview1{
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{//这个位置改成dispatch_sync同步方法,就会打印132,因为任务是提交到主线程中,主线程的runloop是启动的。但是不会立即执行,所以打印132.
        NSLog(@"1---%@",[NSThread currentThread]);
//        [[NSRunLoop currentRunLoop] run]; //添加在此处会打印 13 这个位置是为什么?????????
        [self performSelector:@selector(test1) withObject:nil afterDelay:.0f];
//        [[NSRunLoop currentRunLoop] run];//添加在此处会打印 123 这个位置是为什么?????????
        NSLog(@"3---%@",[NSThread currentThread]);
//        [[NSRunLoop currentRunLoop] run];//添加在此处会打印 132 这个位置是为什么?????????
    });
}
- (void)test1{
    NSLog(@"2---%@",[NSThread currentThread]);
}
//不添加runloop,则打印13。由于dispatch_async开启新线程,新线程默认没有runloop,而performSelector:withObject:afterDelay需要添加计时器到runloop中。如果是performSelector:withObject则打印123,因为不需要计时器添加到runloop中。
- (void)interview2{
    NSThread *thread = [[NSThread alloc] initWithBlock:^{
       NSLog(@"1---%@",[NSThread currentThread]);
        // 解决方法是线程保活
        // 先向当前runloop中添加一个source(如果runloop中一个source、NSTime或Obserer都没有的话就会退出)
        // 然后启动runloop
        //[[NSRunLoop currentRunLoop] addPort:[NSPort new] forMode:NSRunLoopCommonModes];
        //[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    }];
    [thread start];
    [self performSelector:@selector(test2) onThread:thread withObject:nil waitUntilDone:YES];
}
- (void)test2{
    NSLog(@"2---%@",[NSThread currentThread]);
}
//程序会崩溃,由于thread执行完block后就会释放,而test2又被提交到了已经释放的thread上。
- (void)interview3{
    NSLog(@"执行任务1--%@",[NSThread currentThread]);
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{
        NSLog(@"执行任务2--%@",[NSThread currentThread]);
    });
    NSLog(@"执行任务3--%@",[NSThread currentThread]);
}
//死锁,由于任务被同步提交到主队列(串行队列)中。interview3和block形成了互相等待,导致死锁。
- (void)interview4{
    NSLog(@"执行任务1--%@",[NSThread currentThread]);
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_async(queue, ^{
        NSLog(@"执行任务2--%@",[NSThread currentThread]);
    });
    NSLog(@"执行任务3--%@",[NSThread currentThread]);
}
//这和前面一个面试题相比只是把同步函数换成了异步函数。执行完任务1后,通过异步函数添加任务2,虽然异步函数有开启子线程的能力,但是由于是在主队列中,主队列的任务都是在主线程中执行,所以并不会开启子线程。由于是异步函数添加的任务2,所以不必等待任务2就可以继续往下执行,等当前任务(interview4)完成后串行队列再安排执行任务2。所以并不会造成死锁。
- (void)interview5{
    NSLog(@"执行任务1--%@",[NSThread currentThread]);
    dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        NSLog(@"执行任务2--%@",[NSThread currentThread]);
        dispatch_sync(queue, ^{
            NSLog(@"执行任务3--%@",[NSThread currentThread]);
        });
        NSLog(@"执行任务4--%@",[NSThread currentThread]);
    });
    NSLog(@"执行任务5--%@",[NSThread currentThread]);
}
//打印152然后死锁在执行任务3那。
- (void)interview6{
    NSLog(@"执行任务1--%@",[NSThread currentThread]);
    dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue2 = dispatch_queue_create("myqueu2", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        NSLog(@"执行任务2--%@",[NSThread currentThread]);
        
        dispatch_sync(queue2, ^{
            NSLog(@"执行任务3--%@",[NSThread currentThread]);
        });
        
        NSLog(@"执行任务4--%@",[NSThread currentThread]);
    });
    NSLog(@"执行任务5--%@",[NSThread currentThread]);
}
//15234
- (void)interview7{
    NSLog(@"执行任务1--%@",[NSThread currentThread]);
    dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"执行任务2--%@",[NSThread currentThread]);
        dispatch_sync(queue, ^{
            NSLog(@"执行任务3--%@",[NSThread currentThread]);
        });
        NSLog(@"执行任务4--%@",[NSThread currentThread]);
    });
    NSLog(@"执行任务5--%@",[NSThread currentThread]);
}
//15234

面试题参考

NSThread

实现一个常驻线程

NSOperation

对GCD的的封装,特点:

  • 添加依赖
  • 可对任务执行状态控制
  • 控制最大并发

如果重写main方法,底层控制变更任务执行完成状态。如果重写start方法,需要自行控制状态。

参考1 参考1

多线程和锁

OSSpinLock(自旋锁):线程处于盲等状态。一直试图访问锁。持续占用资源,适合轻量访问,比如+、-1等操作。iOS 10弃用,因为存在优先级反转问题。适用os_unfair_lock替代。最好是先增加判断来尝试加锁。 优先级反转:指低优先级的线程先加锁,然后cpu有可能把执行时间都留给高优先级的线程去忙等,造成没有时间去执行低优先级的线程,造成类似死锁的情况。比如thread1低优先级先加锁,然后cpu分配时间给高优先thread2去忙等,那么久会thread1和thread2互相等待。

pthread_mutex(互斥锁):等待互斥锁的线程处于休眠状态,不占用cpu资源,但是唤醒线程需要一定时间,效率比自旋锁低。不会存在优先级反转问题

@synchronized:对递归锁的封装,适用于单例。适用简单性能比较差。

atomic:保证赋值线程安全,不保证操作线程安全。

NSLock:pthread_mutex(常规锁)的封装,控制线程同步问题。

NSRecursiveLock:pthread_mutex(递归锁)的封装,在递归调用中使用到。

NSCondition:pthread_mutex(条件锁)的封装,NSConditionLock也是一个条件锁,它是对NSCondition的进一步封装

dispatch_semaphore_t:是GCD中用于控制最大并发数的,其初始化时设置的信号量值就是最大并发数,当信号量值初始化为1时表示最大并发数为1,也就达到了同一时间只有一个线程访问要加锁代码块的目的,从而实现代码同步。

性能从高到低排序:

  • os_unfair_lock 互斥锁 因为汇编会调用system call系统函数 去休眠
  • OSSpinLock
  • dispatch_semaphore
  • pthread_mutex
  • dispatch_queue(DISPATCH_QUEUE_SERIAL)
  • NSLock
  • NSCondition
  • pthread_mutex(recursive)
  • NSRecursiveLock
  • NSConditionLock
  • @synchronized

什么情况使用自旋锁比较划算?

轻量访问、cpu资源不紧张、多核处理器、等待时间短

什么情况使用互斥锁比较划算?

等待时间长、cpu资源占用高、单核、io操作、代码复杂、竞争紧张