OC面试题目合集地址
问题1: ios当中都有哪些锁
基本的锁:自旋锁
、互斥锁
、读写锁
,其他的比如条件锁
、递归锁
、信号量
都是上层的封装和实现。
-
自旋锁
:循环等待
询问, 不释放当前资源 ( 线程会反复检查变量是否可用), 因为开销很大, 通常用于轻量级数据访问。一旦使用自旋锁, 线程会一直保持该锁, 直到显式释放自旋锁。- OSSpinLock (IOS10之后废弃)
- atomic
-
互斥锁
: 防止2条线程同时对同一公共资源进行读写机制, 保证各个线程互斥在各自的临界区
- @synchronized
- NSLock:
- pthread_mutex
-
条件锁
: 通过条件变量
控制, 不满足资源要求
锁住进行休眠, 满足开锁正常运行- NSCondition
- NSConditionLock
-
递归锁
: 一个线程可以加N次锁而不会引发死锁。递归锁其实是带有递归性质的互斥锁
- pthread_mutex(recursive)
- NSRecursiveLock
-
信号量
: 类似于计算机中记录型信号量
是一种更高级的同步机制,互斥锁
其实是信号量semaphore
仅在0, 1特例, 但信号量semaphore
有更多取值空间, 通常用于实现复杂同步操作, 不仅仅线程间互斥。- dispatch_semaphore_t
读写锁
: 一个读写锁同时只能有一个写者或者多个读者, 其本质是一种特殊的自旋锁
, 提高了并发性。对共享资源
的访问分成读者
和写者
。其中读者
只对共享资源
进行读访问
,写者
则对共享资源
进行写操作
。dispatch_barrier_async
问题2: 说一下锁的性能
锁的性能从高到底依次是:
OSSpinLock(自旋锁) >
dispatch_semaphone(信号量) >
pthread_mutex(互斥锁)>
NSLock(互斥锁)>
NSCondition(条件锁) >
pthread_mutex(recursive 互斥递归锁)>
NSRecursiveLock(递归锁)>
NSConditionLock(条件锁)>
synchronized(互斥锁)
问题3: 看下面例子会发生什么
@interface ViewController ()
@property (nonatomic, strong) NSLock *lock;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.lock = [[NSLock alloc] init];
[self test1];
}
- (void)test1 {
[self.lock lock];
NSLog(@"执行 test1");
[self test2];
[self.lock unlock];
}
- (void)test2 {
[self.lock lock];
NSLog(@"执行 test2");
[self.lock unlock];
}
答案
会发生死锁
使用NSLock对临界区进行加锁, 进入方法2, 方法2里面对同一把锁又进行了lock方法, 这种情况会由于重入原因发生死锁
问题追问: 那上面例子怎么解决
用递归锁解决, 利用其重入特性: 一个线程可以加N次锁而不会引发死锁
@interface ViewController ()
@property (nonatomic, strong) NSRecursiveLock *reclock;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.reclock = [[NSRecursiveLock alloc] init];
[self testA];
}
- (void)testA {
[self.reclock lock];
NSLog(@"执行 testA");
[self testB];
[self.reclock unlock];
}
- (void)testB {
[self.reclock lock];
NSLog(@"执行 testB");
[self.reclock unlock];
}
问题4: 稍微说一说信号量
dispatch_semaphore_t
的关键3个方法
dispatch_semaphore_create(intptr_t value);
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
dispatch_semaphore_signal(dispatch_semaphore_t dsema);
dispatch_semaphore_create(intptr_t value);
: 初始化信号量, 传入参数表示为传入多少信号量-
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
: 操作对信号量-1
- 第一个参数
dsema
: 表示对哪个参数进行操作。即-1
, 如果参数为0, 则等待直到 > 0 - 第二个参数timeout: 表示等待时长
- 第一个参数
dispatch_semaphore_signal(dispatch_semaphore_t dsema);
: 对信号量+1
操作,dsema
参数表示对哪个信号量进行+1
所以看出信号量一般处理 加锁(互斥)
, 异步返回
, 控制线程并发数
这些场景。
问题5: 火车票售卖的设计思路
重点考察多线程并发安全这一情况, 下面例子都以总20张票, 卖10张, 模拟一下:
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, assign) int I;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
// 初始总票数20
self.i = 20;
dispatch_queue_t quene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 模拟或者票抢票, 异步并发
for (int l = 0; l < 10; l++) {
dispatch_async(quene, ^{
[self sellingTickets];
});
}
}
// 卖票方法
- (void)sellingTickets {
sleep(1);
self.i--;
NSLog(@"卖出1张票, 剩余 %d 张", self.i);
}
@end
可看出数据其实是错乱的
那么我们肯定是要加互斥锁, 来保证线程安全, 如下
self.i = 20;
NSLock *lock = [[NSLock alloc] init];
dispatch_queue_t quene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (int l = 0; l < 10; l++) {
dispatch_async(quene, ^{
[lock lock];
[self sellingTickets];
[lock unlock];
});
}
可看出数据正常, 方法没问题是可以的。
这里再提供另外一个解法, 信号量
处理。信号量
优势看下 问题2 锁的性能 , 其中有dispatch_semaphone(信号量)
>NSLock(互斥锁)
。 并且因为这道题数据量比较小, 如果复杂同步操作, 还是用信号量
好一些, 那么我们用信号量
保证下线程安全
self.i = 20;
dispatch_queue_t quene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
for (int l = 0; l < 10; l++) {
dispatch_async(quene, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
[self sellingTickets];
dispatch_semaphore_signal(semaphore);
});
}