多线程以及锁面试题

1.线程依赖有哪些方面可以实现

线程的依赖关系实现线程同步:

1.组队列(dispatch_group_t)

2.阻塞任务(dispatch_barrier_(a)sync)

3.信号量机制(dispatch_semaphore)

2.数据库安全,FMDB的源码, FMDB怎么实现安全机制的,

请添加图片描述

NULL的时候,创建的队列是串行队列

FMDB 提供了 FMDatabaseQueue 在多线程环境下操作数据库,它内部维护了一个串行队列来保证线程安全


通过dispatch_get_specific(kDispatchQueueSpecificKey)获取初始化的_queue。在执行时候,由dispatch_sync(_queue, ^() {

}可知。FMDatabaseQueue是同步执行。

所以_queue是 同步串行队列。

  • FMDatabaseQueue都是用同步的方法来实现
  • FMDatabaseAsynQueue,用异步加线程锁

-(void)inDatabase:(void (^)(FMDatabase *db))block不可以嵌套使用。原理很简单。基于_queue为同步串行队列,如果嵌套使用则会引起死锁

2.1多线程访问 FFMDB怎么操作,加那种锁,为什么要用这种锁

FMDatabaseQueue 的同步串行队列来保证线程安全的

@synchronized 或 NSLock

3.谈下Objective C都有哪些锁机制

具体详解请看这篇文章

NSLock
请添加图片描述

是一种最简单的锁,使用起来也比较简单方便,
可以看到NSLock 其实是pthread_mutex的封装而已
锁跟锁的嵌套造成死锁
加锁和解锁必须在同一个线程

自旋锁 (synchronized)代码块

  • 这种锁是所有锁中最为简单的
  • synchronized 的内部实现是通过传入的对象,为其分配一个递归锁,储存在哈希表中。
  • synchronized有性能方面的劣势(当发现其他线程执行,当前线程就一直询问,一直在忙等,耗费性能比较高。)
  • 小括号里面需要传入一个对象类型,基本数据类型不能做参数
  • 小括号里面这个参数不能为空,如果为nil,就不能保证其锁的功能
  • 对锁的对象要保证生命周期,如果都用self在enter 查找非常麻烦。
  • 同一个线程重复锁
  • 方便简单,安全

pthread

它是一套跨平台的多线程,它是非常强大的多线程锁,可以创建

  • 互斥锁
  • 递归锁
  • 信号量
  • 条件锁
  • 读写锁
  • once锁

在使用时需要导入它的类库
#include <pthread.h>

pthread普通互斥锁

  • pthread_mutex_lock 创建还要进行销毁pthread_mutex_destroy
  • 发现其他线程执行,当前线程休眠(就绪状态)一直等待打开唤醒执行

pthread信号量

  • 当任务比较复杂时
  • pthread使用信号量来实现线程安全也是比较方便的

pthread读写锁

读写锁是一种特殊的自旋锁

将对资源的访问分为读者和写者,
允许多个读者访问同一资源,
却只能有一个写者执行写操作,
读写操作不能同时执行

读写锁保障了读写的安全性和有效性,并且更多的是读操作,由于这种处理,导致读写锁性能比普通锁要稍微低一点,但比较安全。

GCD 的信号量

1.dispatch_semaphore_create:创建一个Semaphore并初始化信号的总量
2.dispatch_semaphore_signal:发送一个信号,让信号总量加1
3.dispatch_semaphore_wait:可以使总信号量减1,当信号总量为0时就会一直等待(阻塞所在线程),否则就可以正常执行。

条件锁( NSConditionLock 与NSCondition

NSConditionLock(状态锁)

状态锁是一种很常见的锁
在多线程操作中,用户可以指定某线程去执行操作,只需要设置对应的状态即可。

NSCondition

锁主要为了当检测条件时保护数据源,执行条件引发的任务;
线程检查器主要是根据条件决定是否继续运行线程,即线程是否被阻塞。

1.先上锁,当其不满足条件时,使其处于wait状态,
2.紧接着写上一些需要做线程安全的代码,
3.然后释放信号量,或者广播一个状态,
足以break刚才的while循环,最后将其解锁,

递归锁( NSRecursiveLock)

可以看到NSRecursiveLock 其实是pthread_mutex的封装而已

  • 同一个线程中,一个锁还没有解锁,就再次加锁,

3.1 为什么synchronized 的性能这么低?

链表的查询,缓存,下层的不断的查找

4. 自旋锁与互斥锁

自旋锁会忙等: 所谓忙等,即在访问被锁资源时,调用者线程不会休眠,而是不停循环在那里,直到被锁资源释放锁。

互斥锁会休眠: 所谓休眠,即在访问被锁资源时,调用者线程会休眠,此时cpu可以调度其他线程工作。直到被锁资源释放锁。此时会唤醒休眠线程。

优缺点:

自旋锁的优点在于,因为自旋锁不会引起调用者睡眠,所以不会进行线程调度,CPU时间片轮转等耗时操作。所有如果能在很短的时间内获得锁,自旋锁的效率远高于互斥锁。

缺点在于,自旋锁一直占用CPU,他在未获得锁的情况下,一直运行--自旋,所以占用着CPU,如果不能在很短的时 间内获得锁,这无疑会使CPU效率降低。自旋锁不能实现递归调用。

自旋锁:atomic、OSSpinLock、dispatch_semaphore_t
互斥锁:pthread_mutex、@ synchronized、NSLock、NSConditionLock 、NSCondition、NSRecursiveLock

5. 怎么实现多个任务执行完后,再统一处理

(应该是同步阻塞、栅栏函数、调度组)

6. 线程之间如何进行通讯

线程间的通信主要体现在,一个线程执行完特定任务后,转到另一个线程去执行任务,在转换任务的同时,将数据也传递给另外一个线程。

NSThread类提供了两个比较常用的方法,用于实现线程间的通信

-(void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
-(void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;


在子线程里面创建一个主线程回到主线程做一些事情

第二个方法是创建一个子线程,将指定的方法放在子线程中运行。

GCD 里面

// 回到主线程
dispatch_async(dispatch_get_main_queue(), ^{

  //我已经回到主线程更新
    });

7.不使用GCD,如何保证线程安全?

8. 线程和进程的区别

进程是一个具有一定独立功能的程序操作系统分配资源的基本单元.

线程是进程的执行单元,进程的所有任务都在线程中执行

线程是CPU 分配资源和调度的最小单位

.同一个进程内的线程共享进程资源

9. GCD有哪些方法api

队列

dispatch_queue_create() // 创建队列
dispatch_get_main_queue() // 主队列
dispatch_get_global_queue() // 全局队列

执行

dispatch_async() // 异步
dispatch_sync() // 同步
dispatch_after() // 延迟
dispatch_once() // 单例
dispatch_apply() // 多次执行
dispatch_barrier_async() // 栅栏函数
dispatch_barrier_sync()

调度组

dispatch_group_create()
dispatch_group_async()
dispatch_group_enter()
dispatch_group_leave()
dispatch_group_notify()
dispatch_group_wait()

信号量

dispatch_semaphore_create()
dispatch_semaphore_wait()
dispatch_semaphore_signal()

调度资源

dispatch_source_create()
dispatch_source_set_timer()
dispatch_source_set_event_handler()
dispatch_resume()
dispatch_suspend()
dispatch_source_cancel()
dispatch_source_testcancel()
dispatch_source_set_cancel_handler()

10.如何实现同步,有多少方式就说多少

  • dispatch_sync()
  • dispatch_barrier_sync()
  • dispatch_group_create() + dispatch_group_wait()
  • dispatch_apple()
  • dispatch_semaphore_create() + dispatch_semaphore_wait()
  • NSOpertaion start
  • NSOperationQueue.maxConcurrentOperationCount = 1
  • 锁 pthread_mutex
  • NSLock
  • NSRecursiveLock
  • NSConditionLock & NSCondition

11. GCD 对比 NSOprationQueue NSThread

GCD 是面向底层的 C 语言的 API , NSOpertaionQueue 用 GCD 构建封装的,是 GCD 的高级抽象。

  • 1、 GCD 执行效率更高,而且由于队列中执行的是由 block 构成的任务,这是一个轻量级的数据结构,写起来更方便
  • 2、 GCD 只支持 FIFO 的队列,而 NSOperationQueue
    可以通过设置最大并发数,设置优先级,添加依赖关系等调整执行顺序
  • 3、 NSOperationQueue 甚至可以跨队列设置依赖关系,但是 GCD 只能通过设置串行队列,或者在队列内添加
    barrier(dispatch_barrier_async) 任务,才能控制执行顺序,较为复杂
  • 4、 NSOperationQueue 因为面向对象,所以支持 KVO ,可以监测 operation 是否正在执行(
    isExecuted )、是否结束( isFinished )、是否取消( isCanceld )

l 实际项目开发中,很多时候只是会用到异步操作,不会有特别复杂的线程关系管理,所以苹果推崇的且优化完善、运行快速的 GCD 是首选

l 如果考虑异步操作之间的事务性,顺序行,依赖关系,比如多线程并发下载,GCD需要自己写更多的代码来实现,而
NSOperationQueue 已经内建了这些支持

l 不论是 GCD 还是 NSOperationQueue
,我们接触的都是任务和队列,都没有直接接触到线程,事实上线程管理也的确不需要我们操心,系统对于线程的创建,调度管理和释放都做得很好。

而 NSThread 需要我们自己去管理线程的生命周期,还要考虑线程同步、加锁问题,造成一些性能上的开销

NSOperationQueue

  • 1、可以添加任务依赖,方便控制执行顺序
  • 2、可以设定操作执行的优先级
  • 3、任务执行状态控制:isReady,isExecuting,isFinished,isCancelled

如果只是重写 NSOperation的 main方法,由底层控制变更任务执行及完成状态,以及任务退出如果重写了 NSOperation的
start 方法,自行控制任务状态

  • 系统通过 KVO的方式移除 isFinished==YES的 NSOperation
  • 4、可以设置最大并发量

NSThread+runloop 实现常驻线程

由于每次开辟子线程都会消耗
cpu,在需要频繁使用子线程的情况下,频繁开辟子线程会消耗大量的cpu,而且创建线程都是任务执行完成之后也就释放了,不能再次利用,那么如何创建一个线程可以让它可以再次工作呢?也就是创建一个常驻线程。

12. performSelector

 dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self performSelector:@selector(test) withObject:nil];
    });

这里的 test 方法是不会去执行的,原因在于

performSelector 要创建任务是要提交到runLoop上去的
而 gcd 底层创建的线程是默认没有开启对应 runloop 的,所有这个方法就会失效。

解决办法

  • dispatch_get_global_queue 改成主队列,由于主队列所在的主线程是默认开启了 runloop 的
  • 就会去执行(将 dispatch_async 改成同步,因为同步是在当前线程执行,那么如果当前线程是主线程,test

13. dispatch_barrier_async

13.1 问:怎么用 GCD 实现多读单写?

这里的写处理就是通过栅栏的形式去写。

就可以用 dispatch_barrier_sync(栅栏函数)去实现

更详细的请看

14. dispatch_group_async

在 n 个耗时并发任务都完成后,再去执行接下来的任务。比如,在 n 个网络请求完成后去刷新 UI 页

15.GCD执行原理?

GCD有一个底层线程池,这个池中存放的是一个个的线程。之所以称为“池”,很容易理解出这个“池”中的线程是可以重用的,当一段时间后这个线程没有被调用胡话,这个线程就会被销毁。注意:开多少条线程是由底层线程池决定的(线程建议控制再3~5条),池是系统自动来维护,不需要我们程序员来维护(看到这句话是不是很开心?)

  • 如果队列中存放的是同步任务,则任务出队后,底层线程池中会提供一条线程供这个任务执行,任务执行完毕后这条线程再回到线程池。这样队列中的任务反复调度,因为是同步的,所以当我们用currentThread打印的时候,就是同一条线程。
  • 如果队列中存放的是异步的任务,(注意异步可以开线程),当任务出队后,底层线程池会提供一个线程供任务执行,因为是异步执行,队列中的任务不需等待当前任务执行完毕就可以调度下一个任务,这时底层线程池中会再次提供一个线程供第二个任务执行,执行完毕后再回到底层线程池中。

这样就对线程完成一个复用,而不需要每一个任务执行都开启新的线程,也就从而节约的系统的开销,提高了效率。在iOS7.0的时候,使用GCD系统通常只能开58条线程,iOS8.0以后,系统可以开启很多条线程,但是实在开发应用中,建议开启线程条数:35条最为合理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值