多线程相关

https://www.cnblogs.com/fengmin/p/5841014.html参考源码解读
[图片上传中...(截屏2019-12-15下午11.26.52.png-95caa7-1576423615938-0)]

多线程面试题:(1)说出多线程的优缺点

多线程方案及优缺点

一.线程间通信以及各自的特点

通信

// 1.创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];

    // 2.添加操作
    [queue addOperationWithBlock:^{
        // 异步进行耗时操作
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
        }

        // 回到主线程
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            // 进行一些 UI 刷新等操作
            for (int i = 0; i < 2; i++) {
                [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
                NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
            }
        }];
    }];
GCD 
performSelectorInbackground:
performSelectorOnMainThread

二.比较:

NSOperation底层是GCD更加面向对象,是可以获得队列执行状态,设置线程依赖,线程优先级,可以取消线程,可以控制最大并发量。
GCD更加简单高效。

二.NSOperation

1,控制最大并发数

// 创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];

// 设置最大并发操作数
//    queue.maxConcurrentOperationCount = 2;
queue.maxConcurrentOperationCount = 1; // 就变成了串行队列
// 添加操作
[queue addOperationWithBlock:^{
    NSLog(@"1-----%@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:0.01];
}];
[queue addOperationWithBlock:^{
    NSLog(@"2-----%@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:0.01];
}];
2,最吸引人的地方是它能添加操作之间的依赖关系

比如说有A、B两个操作,其中A执行完操作,B才能执行操作,那么就需要让B依赖于A

//操作依赖

  • (void)addDependency
    {
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"1-----%@", [NSThread currentThread]);
    }];
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"2-----%@", [NSThread currentThread]);
    }];

    [op2 addDependency:op1]; // 让op2 依赖于 op1,则先执行op1,在执行op2

    [queue addOperation:op1];
    [queue addOperation:op2];
    }
    //可以看到,无论运行几次,其结果都是op1先执行,op2后执行。

三.队列的取消、暂停、和恢复

取消队列的所有操作
-(void)cancelAllOperations;
提示:也可以调用NSOperation的-(void)cancel方法取消单个操作。
暂停和恢复队列
-(void)setSuspended:(BOOL)b; // YES表示暂停队列 NO表示恢复队列
-(BOOL)isSuspend;
获取队列操作数:
operationCount(只读属性)

注意:

(1)暂停不会删除队列内的操作。只是把队列挂起。暂停和挂起都是针对队列而言的。暂停后还可以重新恢复接着原来的任务进行执行。
(2)取消全部任务的操作会清空队列里的所有任务。
(3)暂停和取消都是对队列里的操作而言的,而正在执行的操作是无法取消或暂停的。

三 .GCD

GCD常见函数
截屏2019-12-15下午10.09.57.png
截屏2019-12-15下午10.10.59.png
截屏2019-12-15下午10.11.48.png
截屏2019-12-15下午10.12.53.png
同步,异步,串行,并发

dispatch_sync和dispatch_async用来控制是否要开启新的线程
队列的类型,决定了任务执行的方式(串行,并发)(dispatch_get_main_queue()书串行队列)

各种队列的执行结果
面试题:死锁崩溃

面试题:不会死锁,因为是异步执行 不要求马上在当前线程执行,会等上一个任务完成后再执行

会产生死锁:打印1,5,2死锁崩溃
不会产生死锁,添加的是不同的队列
不会产生死锁。5的顺序不一定
dispatch_get_global_queue(0,0)全局并发队列只有一个 内存地址相同的
面试题解决方法在下方

需要开启runloop,需要写在perform下面,因为runloop在无事处理的时候会休眠,所以添加事件后 再启动它
面试题:打印1 崩溃,崩溃日志 目标线程退出当等待执行perform的时候。解决方案,唤醒block块子线程的runloop
队列组面试题

还有一种情况,先并发同时执行任务1,任务二,等他俩执行完,再通知任务三任务四并发执行。就是下面的截图代码。两个notify被通知到,同时在queue这个并发队列里去执行任务即可!


队列组面试答案

dispatch_group_enter :通知 group,下个任务要放入 group 中执行了
dispatch_group_leave: 通知 group,任务成功完成,要移除,与 enter成对出现
dispatch_group_wait: 在任务组完成时调用,或者任务组超时是调用(完成指的是enter和leave次数一样多)
dispatch_group_notify: 只要任务全部完成了,就会在最后调用

- (void)test {
    NSURL *url = [NSURL URLWithString:@"http://upload-images.jianshu.io/upload_images/1432482-dcc38746f56a89ab.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"];

    SDWebImageManager *manager = [SDWebImageManager sharedManager];

    dispatch_group_t group = dispatch_group_create();

    dispatch_group_enter(group);
    [manager loadImageWithURL:url options:SDWebImageRefreshCached progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
        dispatch_group_leave(group);
    }];

    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"下载完成了");
    });
}
//信号量 也可以用信号量的方式去做
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    //创建全局并行
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //任务一
    dispatch_group_async(group, queue, ^{
        [self getAdvertList:^(BOOL iscomple) {
            dispatch_semaphore_signal(semaphore);
        }];
    });
    //任务二
    dispatch_group_async(group, queue, ^{
        [self getHotCultureList:^(BOOL iscomple) {
            dispatch_semaphore_signal(semaphore);
        }];
    });
 
 
    dispatch_group_notify(group, queue, ^{
 
        //6个任务,6个信号等待.
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        //这里就是所有异步任务请求结束后执行的代码
        //[self.homeTableView.mj_header endRefreshing];
        //这里两个网络请求结束后。获取到的。一个参数用于第三个借口参数
        NSLog(@"------------4444444444444444-xin hao-------%@", self.imeiStr);

多线程安全

不是所有多线程都加锁,如果只是读取数据,那么不会对数据造成安全隐患,当多个线程对数据同时读写的时候才需要加锁保证线程安全

多线程的安全隐患
解决线程安全隐患:常见的线程同步技术就是:加锁
截屏2019-12-16上午12.56.05.png

锁:

(1)OSSpinLock 自旋锁(high level高级锁 不休眠)
可能会出现优先级反转的情况:它是自旋锁,就是CPU分配时间片给它,资源被占用它不会进入休眠状态,会进入忙等状态,由于优先级低的加锁进去执行了,而CPU会优先分配优先级高的时间片,所以优先级低的可能一直得不到时间片,得不到时间片就无法继续执行也就无法解锁 不解锁优先级高的又进不来,所以会造成类似死锁的状态,优先级高的实际一直得不到执行。使用

static声明的变量是在编译时确定的 不是在运行时,所以不能直接函数调用,函数是在运行时执行的,static声明的变量 只初始化一次,如果一开始不给他赋值 那么可以如下图所示用once
OSSpinLock因为是忙等机制,所以效率高,不会休眠

(2) os_unfair_lock(它是互斥锁)

os_unfair_lock的介绍:注意:如果没解锁那么会进入死锁,永远拿不到锁,来访问的线程就休眠

(3)pthread_mutex

pthread_mutext介绍:有默认锁normal和递归锁recursive两种模式,(递归锁允许同一个线程对一把锁进行重复加锁)递归锁开锁和锁相对应就行。是保证线程安全的,保证线程同步的

条件,有条件就可以不用调用unlock来放开这把锁
递归锁使用:递归就是自己调用自己

生产者-消费者模式:同时进行的,但是需要先生产出产品才可以卖给消费者

总结:
- (instancetype)init
{
    if (self = [super init]) {
        // 初始化属性
        pthread_mutexattr_t attr;
        pthread_mutexattr_init(&attr);
        pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
        // 初始化锁
        pthread_mutex_init(&_mutex, &attr);
        // 销毁属性
        pthread_mutexattr_destroy(&attr);
        
        // 初始化条件
        pthread_cond_init(&_cond, NULL);
        
        self.data = [NSMutableArray array];
    }
    return self;
}

- (void)otherTest
{   //子线程同时进行 所以不确定先调用哪个方法
    [[[NSThread alloc] initWithTarget:self selector:@selector(__remove) object:nil] start];
    
    [[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start];
}

// 生产者-消费者模式

// 线程1
// 删除数组中的元素
- (void)__remove
{
    pthread_mutex_lock(&_mutex);
    NSLog(@"__remove - begin");
    
    if (self.data.count == 0) {
        // 等待  ⚠️⚠️⚠️等待期间会放开这把锁,这个是可能就会调用添加方法给它加锁,当添加完调用signal,就会唤醒加这个条件的锁的线程,注意⚠️其实这个时候是继续等待状态等待别人放开这把锁,也是等添加方法执行unlock解开这个锁的时候,pthread_cond_wait它才可以再次加锁,就继续往下走去remove,执行完了解开这个锁。
        pthread_cond_wait(&_cond, &_mutex);
    }
    
    [self.data removeLastObject];
    NSLog(@"删除了元素");
    
    pthread_mutex_unlock(&_mutex);
}

// 线程2
// 往数组中添加元素
- (void)__add
{
    pthread_mutex_lock(&_mutex);//初始化锁
    
    sleep(1);
    
    [self.data addObject:@"Test"];
    NSLog(@"添加了元素");
    
    // 信号 激活这个等待条件的线程
    pthread_cond_signal(&_cond);
    // 广播
//    pthread_cond_broadcast(&_cond);
    
    pthread_mutex_unlock(&_mutex);
}

- (void)dealloc
{   //销毁资源
    pthread_mutex_destroy(&_mutex);
    pthread_cond_destroy(&_cond);
}

(4)NSLock,NSRecursiveLock。NSRecursiveLock递归锁表示可处理同一方法内部多次上锁,表示它可以允许同一线程对其多次加锁,而不会引起死锁的问题,主要用在循环和递归调用中。也是基于mutex实现的,所以效率肯定低于mutex

lockBeforeDate:表示在这个时间之前等待 到了这个时间还没有得到这个锁的话,那么会加锁失败

(5)NSCondition

NSCondition:基于mutex的都是互斥锁。一般用于多线程同时访问、修改同一个数据源,保证在同一时间内数据源只被访问、修改一次,其他线程的命令需要在lock 外等待,只到unlock ,才可访问
@interface NSConditionDemo()
@property (strong, nonatomic) NSCondition *condition;
@property (strong, nonatomic) NSMutableArray *data;
@end

@implementation NSConditionDemo

- (instancetype)init
{
    if (self = [super init]) {
        self.condition = [[NSCondition alloc] init];
        self.data = [NSMutableArray array];
    }
    return self;
}

- (void)otherTest
{
    [[[NSThread alloc] initWithTarget:self selector:@selector(__remove) object:nil] start];
    
    [[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start];
}

// 生产者-消费者模式
// 线程1
// 删除数组中的元素
- (void)__remove
{
    [self.condition lock];
    NSLog(@"__remove - begin");
    
    if (self.data.count == 0) {
        // 等待
        [self.condition wait];
    }
    
    [self.data removeLastObject];
    NSLog(@"删除了元素");
    
    [self.condition unlock];
}

// 线程2
// 往数组中添加元素
- (void)__add
{
    [self.condition lock];
    
    sleep(1);
    
    [self.data addObject:@"Test"];
    NSLog(@"添加了元素");
    // 信号
    [self.condition signal];
    
    // 广播
//    [self.condition broadcast];
    [self.condition unlock];
    
}
注意
线程保活

不是所有的都给runloop去做,像NSlog是不需要的

(6)NSConditionLock

NSConditionLock
可以添加线程依赖,这个线程的开始依赖于另一个线程的完成

(6)dispatch_queue

dispatch_queue
直接使用串行队列也是加锁

(6) dispatch_semaphore

注意:wait和signal的顺序。当创建时value为0时那么需要先signal+1,然后再wait。注意使用顺序和信号量的关系
• dispatch_semaphore_create(value)
创建信号量,value一般情况下传0
• dispatch_semaphore_wait()
等待信号量,会对信号量减1(value - 1),当信号量 < 0 时,会阻塞当前线程,等待信号(signal),当信号量 >= 0时,会执行wait后面的代码
• dispatch_semaphore_signal()
信号量加1,当信号量 >= 0 会执行wait之后的代码。
因此dispatch_semaphore_wait()和dispatch_semaphore_signal()要成对使用。

dispatch_semaphore
dispatch_semaphore

(7)synchronized


synchronized
可以递归加锁

加锁同步方案性能比较 面试!

iOS线程同步方案性能比较:unfair_lock性能最高,但是iOS10才有的,如果考虑到老版本支持的话,建议用dispatch_semaphore支持iOS8,OSSpinLock现在不建议使用了,它是自旋锁可能会引起死锁。递归锁为了保证内部可以递归调用效率会弱一点。一般推荐信号量和mu tex(跨平台),NSCondition和NSLock更加面向对象都是对mutex的封装,效率肯定不如mutex

面试题

自旋锁,互斥锁的比较

(2)atomic

atomic只是对赋值setter取值getter方法加自旋锁spinLock,并不能保证它使用过程中是线程安全的。比如动态数组增删改不能保证,比如买票这个案例多个线程同时访问票数-1,最后很有可能票会多卖了,结合CPU分配时间片的原理
iOS中的读写安全方案
pthread_rwlock实现多读单写
pthread_rwlock实现多读单写代码:从打印时间可以看出读可以多个同时读,单是写是只有一个,写的同时也不会读数据。这个读写锁已经帮我做好了。

dispatch_barrier_async实现多读单写
用dispatch_barrier_async调用的时候(注意必须是自己创建的并发队列),会建立起一个屏障,队列中就只有这一个任务在执行,这个任务执行完,多读的异步并发才会继续执行
dispatch_barrier_async实现多读单写代码
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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