多线程

方案 简介 语言 生命周期 实用频率
pthread 跨平台(Unix,Linux,Windows)
更底层
C 语言 程序员管理 很少使用
NSThread 面向对象,简单易用 OC 语言 程序员管理 偶尔实用
GCD 旨在代替 NSThread 等线程技术
充分利用设备的多核
C 语言 自动管理 经常使用
NSOperation 基于 GCD 的封装,更加面向对
比 GCD 多了一些简单实用的功能
OC 语言 自动管理 经常使用

GCD

异步方式执行任务

dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

同步方式执行任务

dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);

GCD 队列

  • 并发队列
    • 让多个任务同时执行(自动开启多个线程同时执行任务)
    • 并发功能只有在异步(dispatch_async)函数下才有效
  • 串行队列
    • 让任务一个接着一个的执行(一个任务执行完毕后,在执行下一个任务)

术语:同步 异步 并发 串行

  • 同步和异步:能不能开启新的线程
    • 同步:在当前线程中执行任务,不具备开启新线程的能力
    • 异步:在新的线程中执行任务,具备开启新线程的能力
  • 并发和串行:任务的执行方式
    • 并发:多个任务同时执行
    • 串行:一个接一个执行

小总结:
==dispatch_sync 和 dispatch_async 具备了开启线程的能力
队列的类型:决定了任务执行方式:并发,串行==

==dispatch_sync:要求立马执行任务
dispatch_async:不要求立马执行任务,可以等上一个任务执行完成再执行==

- 并发队列 串行队列 主队列
同步 ==没有==开启新线程
==串行==执行任务
==没有==开启新线程
==串行==执行任务
==没有==开启新线程
==串行==执行任务
异步 ==有==开启新线程
==并发==执行任务
==有==开启新线程
==串行==执行任务
==没有==开启新线程
==串行==执行任务

死锁

// 问题:以下代码在主线程执行,会不会产生死锁?
NSLog(@"执行任务 1");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
    NSLog(@"执行任务 2");
});
NSLog(@"执行任务 3");
    
答案:会,dispatch_get_main_queue 主队列,表示在主线程执行,在队列中 FIFO,先进先出,所以:dispatch_sync 要求立马执行,queue 请求一个一个的执行,但是当前 任务 3 还没有执行完,所以冲突了
// 问题:以下代码在主线程执行,会不会产生死锁?
NSLog(@"执行任务 1");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{ // block0
    NSLog(@"执行任务 2");
    dispatch_sync(queue, ^{ // block1
        NSLog(@"执行任务 3");
    });
    NSLog(@"执行任务 4");
});
   NSLog(@"执行任务 5");
    
答案:会,block1要求立马执行,但是block0还没有结束,所以死锁
怎么样不死锁:将block1放到另一个queue中

小总结:
==死锁==:使用 sync当前 串行 队列中添加任务,会死锁

Lock 锁

  • OSSPinLock 自旋锁,等待锁的线程会处于忙等状态,一直占用CPU资源(高级锁,一直等),相当于while(是否加锁){}(iOS10以上弃用了,提示用os_unfair_lock代替)

    • 目前已经不再安全,可能会出现优先级翻转问题
    • 如果等待锁的线程优先级较高,它会一直占着CPU资源,优先级低的线程就无法释放锁
    #import 
        
    // 初始化
    OSSpinLock lock = OS_SPINLOCK_INIT;
        
    // 尝试加锁(如果需要等待就不加锁, 直接返回 false,如果不需要等待就加锁,返回 true)
    // OSSpinLockTry(&lock)
        
    // 加锁
    OSSpinLockLock(&lock);
        
    // 解锁
    OSSpinLockUnlock(&lock);
    
  • os_unfair_lock 互斥锁,线程会处于休眠状态,并非忙等(低级锁,会休眠),用于取代OSSpinLock, 从iOS10开始

    #import 
        
    // 初始化
    os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
        
    // 尝试加锁(如果需要等待就不加锁,直接返回 false,如果不需要等待就加锁,返回 true
    // os_unfair_lock_trylock(&lock)
        
    // 加锁
    os_unfair_lock_lock(&lock);
        
    // 解锁
    os_unfair_lock_unlock(&lock);
    
  • pthread_mutex 互斥锁,等待锁的线程会处于休眠状态(低级锁,会休眠)

    • 普通锁(PTHREAD_MUTEX_DEFAULT)
    • 递归锁(PTHREAD_MUTEX_RECURSIVE),允许同一个线程对一把锁进行重复加锁
    • 错误锁,解决出错时使用,不常用

    递归锁:
    线程1 执行test(),加锁,继续 执行test(),加锁... 可以
    线程2 执行test(),但是线程1已经加锁,只能等待

    #import 
        
    // 初始化
    pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; // 结构体只能在定义的时候进行复制,不能先定义,再赋值
    // 或
    {
        // 先初始化一个属性
        pthread_mutexattr_t attr;
        pthread_mutexattr_init(&attr); 
        pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
        pthread_mutex_init(&lock, &attr); // 初始化锁的时候带有属性,属性为NULL,则默认Default
        pthread_mutexattr_destroy(&attr); // 销毁属性
    }
        
    // 尝试加锁(如果需要等待就不加锁,直接返回 false,如果不需要等待就加锁,返回 true
    // pthread_mutex_trylock(&lock)
        
    // 加锁
    pthread_mutex_lock(&lock);
        
    // 解锁
    pthread_mutex_unlock(&lock);
        
    // 销毁锁
    pthread_mutex_destroy(&lock)
        
        
    // 条件锁
    pthread_cond_init(&_cond, NULL); // 初始化一个条件
    pthread_cond_wait(&_cond, &_lock); // 等待信号(休眠),释放锁,让其他线程先运行
    pthread_cond_signal(&_cond);  // 发信号
    pthread_cond_broadcast(&_cond); // 发广播,对应多个线程等待
    pthread_cond_destroy(&_cond); // 销毁条件
    
  • NSLock、NSRecursiveLock、NSCodition、NSCoditionLock

    • NSLock 是对 mutex 普通锁的封装
    • NSRecursiveLock 是对 mutex 的递归锁的封装
    • NSCodition 是对 mutex 和 cond 的封装,
    • NSCoditionLock 对 NSCodition,在线程中间等待 的进一步封装,在线程开始前等待,按顺序执行线程(线程同步)
  • dispatch_semaphore

    • 线程同步
    • 控制最大并发量
    // 创建信号量为 5 的 semaphore
    dispatch_semaphore_t semaghore = dispatch_semaphore_create(5);
        
    // 如果信号量 > 0, 信号量 -1,继续执行
    // 如果信号量 <= 0, 线程休眠等待
    dispatch_semaphore_wait(semaghore, DISPATCH_TIME_FOREVER);
    
    // 信号量 +1
    dispatch_semaphore_signal(semaghore);
    
  • @synchronized

    • 线程同步
    • synchronized 对 mutex 的封装,底层封装比较复杂,使用 HashMap,用对象作为 key,用封装 mutex 的对象作为 value,
    • 递归锁,可以循环套用

    加锁性能比较

    性能 高 -> 低
    os_unfair_lock      - 互斥锁,等待时会休眠,iOS10 以上
    OSSPinLock          - 自旋锁,等待时不会休眠,不推荐使用,不安全
    dispatch_semaphore  - 信号量,线程同步,推荐使用
    pthread_mutex       - 互斥锁,等待时会休眠,推荐使用
    dispatch_queue(SERIAL) - 线程同步
    NSLock              - 封装 mutex,面向对象
    NSCodition          - 封装 mutex-cond,面向对象
    NSRecursiveLock     - 递归锁,封装 mutex-PTHREAD_MUTEX_RECURSIVE,不推荐使用
    NSCoditionLock     - 条件锁,封装 mutex-PTHREAD_MUTEX_RECURSIVE,不推荐使用
    @synchronized
    

    推荐:pthread_mutex(跨平台,iOS所有版本都支持)、os_unfair_lock(iOS10以上)、dispatch_semaphore(iOS所有版本)

读写安全/多读单写

要求:
读写互斥
谢谢互斥
读读互斥

方案一:使用 pthread_rwlock 读写锁

#import 

@property (nonatomic, assign) pthread_rwlock_t lock;

// 初始化
pthread_rwlock_init(&_lock, NULL);

- (void)read {
    pthread_rwlock_rdlock(&_lock);
    //pthread_rwlock_tryrdlock(&_lock);
    NSLog(@"%s", __func__);
    sleep(2);
    pthread_rwlock_unlock(&_lock);
}

- (void)write {
    pthread_rwlock_wrlock(&_lock);
    pthread_rwlockpthread_rwlock_rdlocksleep(1);
    NSLog(@"%s", __func__);
    sleep(3);
    pthread_rwlock_unlock(&_lock);// 解锁
}
- (void)dealloc {
    // 销毁读写锁
    pthread_rwlockattr_destroy(&_lock);
}

方案二:使用 dispatch_barrier_async 异步栅栏调用

dispatch_barrier_async 同一个队列中,要求在执行barrier中的任务时,不允许其他任务执行

self. queue = dispatch_queue_create("rw_queue", DISPATCH_QUEUE_CONCURRENT);

- (void)read {
    dispatch_sync(self.queue, ^{ // 这里可以同步也可以异步,看需求,如果有返回值就需要同步返回,如果直接在block中处理数据,可以使用异步
        sleep(1);
        NSLog(@"read");
    });
}

- (void)write {
    dispatch_barrier_async(self.queue, ^{
        sleep(5);
        NSLog(@"write");
    });
}

面试题

1、你理解的多线程?

答案:
多线程是为了同步完成多项任务,不是为了提高运行效率,而是为了提高资源使用效率来提高系统的效率。多线程是在同一时间需要完成多项任务的时候实现的。
优点:

1)、可以加快程序的运行速度,因为用户界面可以在进行其它工作的同时一直处于活动状态
2)、可以把占据长时间的程序中的任务放到后台去处理
3)、当前没有进行处理的任务时可以将处理器时间让给其它任务
4)、可以并发执行多个任务,释放一些珍贵的资源如内存占用等等
5)、可以随时停止任务
6)、可以分别设置各个任务的优先级以优化性能
    
缺点:
1)、因为多线程需要开辟内存,而且线程切换需要时间因此会很消耗系统内存。
2)、线程的终止会对程序产生影响
3)、由于多个线程之间存在共享数据,因此容易出现线程死锁的情况
4)、对线程进行管理要求额外的CPU开销。线程的使用会给系统带来上下文切换的额外负担。

2、你在项目中用过 GCD 吗?

答案:
开启子线程,使用信号量,栅栏函数,timer等等,onetoken

3、GCD 的队列类型

答案:
并发队列
- 让多个任务同时执行(自动开启多个线程同时执行任务)
- 并发功能只有在异步(dispatch_async)函数下才有效
串行队列
- 让任务一个接着一个的执行(一个任务执行完毕后,在执行下一个任务)

4、说一下 OperationQueue 和 GCD 的区别,以及各自的优势

答案:
GCD 是底层的C语言构成的 API,在队列中执行的是由block构成的任务,这是一个轻量级的数据结构
NSOperation 是对 GCD 的封装,更加面向对象,为我们提供了更多的选择,更简单的API入口

5、线程安全的处理手段有哪些?

答案:
@synchronized 关键字与 Lock 锁方式
synchronied 会自动释放锁;而 Lock 需手动为代码块加锁并释放锁
从性能上,Lock锁方式优于synchronized关键字

6、OC 你了解的锁有哪些?

答案:
os_unfair_lock      - 互斥锁,等待时会休眠,iOS10 以上
OSSPinLock          - 自旋锁,等待时不会休眠,不推荐使用
dispatch_semaphore  - 信号量,线程同步,推荐使用
pthread_mutex       - 互斥锁,等待时会休眠,推荐使用
dispatch_queue(SERIAL) - 线程同步
NSLock              - 封装 mutex,面向对象
NSCodition          - 封装 mutex-cond,面向对象
NSRecursiveLock     - 递归锁,封装 mutex-PTHREAD_MUTEX_RECURSIVE,不推荐使用
NSCoditionLock     - 条件锁,封装 mutex-PTHREAD_MUTEX_RECURSIVE,不推荐使用
@synchronized

追问一:自旋锁和互斥锁对比

答案:
自旋锁 - 等待锁的线程会处于忙等状态,一直占用 CPU 资源,如果等待锁的线程优先级较高,它会一直占着 CPU 资源,优先级低的线程就无法释放锁
互斥锁 - 线程会处于休眠状态
    
什么情况使用自旋锁?
预计线程等待锁的时间很短
加锁的代码(临界区)经常被调用,但竞争情况很少出现
CPU 资源不紧张
多核处理器
    
什么时候使用互斥锁?
预计线程等待锁的时间很长
单核处理器
临界区 IO 操作
临界区代码复杂或循环量大
临界区竞争非常激烈

7、打印结果

dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
    NSLog(@"1");
    // test 方法实现 NSLog(@"2");
    [self performSelector:@selector(test) withObject:nil afterDelay:.0];
    NSLog(@"3");
});
答案:1,3  
理由: performSelector:withObject:afterDelay: 的本质是往 RunLoop 中添加定时器, 而子线程默认没有启动 RunLoop

你可能感兴趣的:(多线程)