iOS中几种锁的总结

    iOS开发中,为了提升程序的运行效率,增强用户体验,我们经常使用多线程,在使用多线程中,我们应该尽量避免资源在线程中共享,减少线程中的相互作用,我们采用锁的机制来确保线程安全。

线程安全

    当一个线程访问数据的时候,其他的线程不能对其进行数据访问,更不能对其进行数据修改,直到该线程访问完毕。即,同一时刻,对同一个数据的操作的线程只有一个。只有这样,才能使数据不被其他线程污染;而线程不安全,则是同一时刻可以有多个线程对该数据进行访问,从而得不到预期的结果。

    比如读写数据库,当一个线程在写数据的时候,如果这个时候另一个线程来直接读取数据,那么有可能得到的将是不可预期的结果。

    通常我们使用锁的机制来保证线程安全,即确保同一时刻只有同一个线程来对同一个数据源进行访问。在开发中我们经常使用以下几种锁:

  1. NSLock
  2. @synchronized
  3. NSRecursiveLock
  4. NSCondition
  5. NSConditionLock
  6. pthread_mutex
  7. pthread_rwlock
  8. dispatch_semaphore
  9. OSSpinLock

NSLock

NSLock实现了最基本的互斥锁,遵循了NSLocking协议,通过 lock 和 unlock 来进行锁定和解锁,其使用也非常简单

1
2
3
4
5
6
NSLock *lock = [[NSLock alloc]init];
[lock lock];
NSLog(@"加锁成功");
sleep(2);
[lock unlock];
NSLog(@"解锁成功");

注意:lock与unlock操作必须在同一线程,否则结果不确定甚至会引起死锁

由于是互斥锁,当一个线程进行访问的时候,该线程获得锁,其他线程进行访问的时候,将被操作系统挂起,直到该线程释放锁,其他线程才能对其进行访问,从而却确保了线程安全。但是如果连续锁定两次,则会造成死锁问题。

@synchronized

@synchronized是 iOS 中最常见的锁,用法很简单:

1
2
3
4
5
6
 - (void)synchronizeDemo {
NSObject *object = [[NSObject alloc]init];
@synchronized(object) { //这里添加一个OC对象,一般使用self
// Everything between the braces is protected by the @synchronized directive.
}
}

注:1.加锁的代码尽量少
   2.添加的OC对象必须在多个线程中都是同一对象,下面举一个反例

我们看一个经典的卖票例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
- (void)viewDidLoad {
[super viewDidLoad];
// 设置车票总张数10 张
_tickets = 5;

// 线程一
NSThread *threadOne = [[NSThread alloc]initWithTarget:self selector:@selector(saleTickets) object:nil];

// 线程二
NSThread *threadTwo = [[NSThread alloc]initWithTarget:self selector:@selector(saleTickets) object:nil];

// 开启线程
[threadOne start];
[threadTwo start];
}

- (void)saleTickets {
NSObject *object = [[NSObject alloc]init];
while (1) {
@synchronized(object) {
[NSThread sleepForTimeInterval:1];
if (_tickets > 0) {
_tickets --;
NSLog(@"剩余票数= %ld",_tickets);
} else {
NSLog(@"票卖没了");
break;
}
}
}
}

结果卖票出错了,出现这个原因的问题是每个线程都会创建一个object对象,锁后面加的object在不同线程中了从而导致卖票出差。

把@synchronized(object)改成 @synchronized(self)就能得到了正确结果