多线程

方案 简介 语言 生命周期 实用频率
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 <libkern/OSAtomic.h>
        
    // 初始化
    OSSpinLock lock = OS_SPINLOCK_INIT;
        
    // 尝试加锁(如果需要等待就不加锁, 直接返回 false,如果不需要等待就加锁,返回 true)
    // OSSpinLockTry(&lock)
        
    // 加锁
    OSSpinLockLock(&lock);
        
    // 解锁
    OSSpinLockUnlock(&lock);
    
  • os_unfair_lock 互斥锁,线程会处于休眠状态,并非忙等(低级锁,会休眠),用于取代OSSpinLock, 从iOS10开始

    #import <os/lock.h>
        
    // 初始化
    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.h>
        
    // 初始化
    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 <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
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342