多线程

2020-09-27  本文已影响0人  weixhe
方案 简介 语言 生命周期 实用频率
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_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 锁

读写安全/多读单写

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

方案一:使用 pthread_rwlock 读写锁

#import <pthread.h>

@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
上一篇 下一篇

猜你喜欢

热点阅读