线程安全、锁

nonatomicatomic

  • nonatomic: 不会对生成的 gettersetter 方法加同步锁(非原子性)
  • atomic: 会对生成的 gettersetter 加同步锁(原子性)
    setter / getteratomic 修饰的属性时,该属性是读写安全的。然而读写安全并不代表线程安全。

线程安全概念(thread safety)

  • 线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。
  • 线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。

验证 atomic 非线程安全

如下代码

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
@interface ViewController ()
@property (atomic, copy) NSString *name;
@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];

dispatch_async(dispatch_get_global_queue(0, 0), ^{
while (1) {
self.name = @"kyson";
NSLog(@"kyson == %@", self.name);
}
});

dispatch_async(dispatch_get_global_queue(0, 0), ^{
while (1) {
self.name = @"shen";
NSLog(@"shen == %@", self.name);
}
});
}

@end

输出结果

1
2
3
4
5
6
7
8
2018-07-09 15:28:05.681759+0800 YSThreadSafetyTest[3186:1387832] kyson == kyson
2018-07-09 15:28:05.681857+0800 YSThreadSafetyTest[3186:1387942] shen == shen
2018-07-09 15:28:05.681956+0800 YSThreadSafetyTest[3186:1387832] kyson == kyson
2018-07-09 15:28:05.721291+0800 YSThreadSafetyTest[3186:1387942] shen == shen
2018-07-09 15:28:05.721291+0800 YSThreadSafetyTest[3186:1387832] kyson == shen
2018-07-09 15:28:06.347283+0800 YSThreadSafetyTest[3186:1387942] shen == shen
2018-07-09 15:28:06.386894+0800 YSThreadSafetyTest[3186:1387832] kyson == kyson
2018-07-09 15:28:06.386894+0800 YSThreadSafetyTest[3186:1387942] shen == kyson

第五行和第8行可以看出, atomic 非线程安全。

  • 也就是说 atomic 只能做到读写安全并不能做到线程安全,若要实现线程安全还需要采用更为深层的锁定机制才行。
  • iOS开发时一般都会使用 nonatomic 属性,因为在iOS中使用同步锁的开销较大,这会带来性能问题,但是在Mac OS X程序时,使用 atomic 属性通常都不会有性能瓶颈。

锁的概念

在计算机科学中,锁是一种同步机制,用于在存在多线程的环境中实施对资源的访问限制。

锁的作用

  • 通俗来讲:就是为了防止在多线程的情况下对共享资源的脏读或者脏写。
  • 也可以理解为:执行多线程时用于强行限制资源访问的同步机制,即并发控制中保证互斥的要求。

iOS开发中常用的锁

  • @synchronized
  • NSLock 对象锁
  • NSRecursiveLock 递归锁
  • NSConditionLock 条件锁
  • pthread_mutex 互斥锁(C语言)
  • dispatch_semaphore 信号量实现加锁(GCD)
  • OSSpinLock 自旋锁

性能:ibireme 大神博客盗的性能图 =-=

@synchronized

@synchronized 其实是一个 OC 层面的锁, 主要是通过牺牲性能换来语法上的简洁与可读性。
@synchronized 是我们平常使用最多的但是性能最差的。

1
2
3
4
5
6
7
8
9
// OC
@synchronized(self) {
// 需要执行的代码块
}

// Swift
objc_sync_enter(self)
// 需要执行的代码块
objc_sync_exit(self)

如下代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
dispatch_async(dispatch_get_global_queue(0, 0), ^{
@synchronized(self) {
NSLog(@"第一个线程同步操作开始");
sleep(3);
NSLog(@"第一个线程同步操作结束");
}
});

dispatch_async(dispatch_get_global_queue(0, 0), ^{
sleep(1);
@synchronized(self) {
NSLog(@"第二个线程同步操作");
}
});

输出结果

1
2
3
2018-07-09 15:42:06.226069+0800 YSThreadSafetyTest[3294:1487479] 第一个线程同步操作开始
2018-07-09 15:42:09.226758+0800 YSThreadSafetyTest[3294:1487479] 第一个线程同步操作结束
2018-07-09 15:42:09.226942+0800 YSThreadSafetyTest[3294:1487480] 第二个线程同步操作

  • @synchronized(self) 指令使用的 self 为该锁的唯一标识,只有当标识相同时,才为满足互斥,如果线程2中的 self 改成其它对象,线程2就不会被阻塞。
  • @synchronized 指令实现锁的优点就是我们不需要在代码中显式的创建锁对象,便可以实现锁的机制,但作为一种预防措施,@synchronized 块会隐式的添加一个异常处理来保护代码,该处理会在异常抛出的时候自动的释放互斥锁。所以如果不想让隐式的异常处理例程带来额外的开销,你可以考虑使用锁对象。
  • 如果在 @sychronized(object){} 内部 object 被释放或被设为 nil,从我做的测试的结果来看,的确没有问题,但如果 object 一开始就是 nil,则失去了锁的功能。不过虽然 nil 不行,但 @synchronized([NSNull null]) 是完全可以的。

NSLock 对象锁

  1. NSLock 中实现了一个简单的互斥锁。通过 NSLocking 协议定义了 lockunlock 方法。
1
2
3
4
@protocol NSLocking
- (void)lock;
- (void)unlock;
@end

代码示例:银行取钱

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
- (void)nslockTest {
_money = 1000;
_lock = [[NSLock alloc] init];

dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self drawMoney:@"小明"];
});

dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self drawMoney:@"小红"];
});
}

- (void)drawMoney:(NSString *)person {
while (1) {
sleep(1);
// 加锁
[_lock lock];
if (_money > 0) {
_money -= 200;
NSLog(@"%@ 取出200元,剩余:%zd元", person, _money);
} else {
NSLog(@"%@ 小明 没钱了,勿取", person);
break;
}
// 解锁
[_lock unlock];
}
}

加锁结果:

1
2
3
4
5
6
2018-07-09 16:04:06.603614+0800 YSThreadSafetyTest[3547:1618383] 小红 取出200元,剩余:800元
2018-07-09 16:04:06.603813+0800 YSThreadSafetyTest[3547:1618389] 小明 取出200元,剩余:600元
2018-07-09 16:04:07.608957+0800 YSThreadSafetyTest[3547:1618383] 小红 取出200元,剩余:400元
2018-07-09 16:04:07.609166+0800 YSThreadSafetyTest[3547:1618389] 小明 取出200元,剩余:200元
2018-07-09 16:04:08.611343+0800 YSThreadSafetyTest[3547:1618389] 小明 取出200元,剩余:0元
2018-07-09 16:04:08.611538+0800 YSThreadSafetyTest[3547:1618383] 小红 小明 没钱了,勿取

不加锁结果:

1
2
3
4
5
6
7
2018-07-09 16:05:10.608726+0800 YSThreadSafetyTest[3565:1624593] 小红 取出200元,剩余:600元
2018-07-09 16:05:10.608736+0800 YSThreadSafetyTest[3565:1624590] 小明 取出200元,剩余:800元
2018-07-09 16:05:11.612305+0800 YSThreadSafetyTest[3565:1624593] 小红 取出200元,剩余:200元
2018-07-09 16:05:11.612305+0800 YSThreadSafetyTest[3565:1624590] 小明 取出200元,剩余:400元
2018-07-09 16:05:12.616221+0800 YSThreadSafetyTest[3565:1624593] 小红 小明 没钱了,勿取
2018-07-09 16:05:12.616220+0800 YSThreadSafetyTest[3565:1624590] 小明 取出200元,剩余:0元
2018-07-09 16:05:13.617584+0800 YSThreadSafetyTest[3565:1624590] 小明 小明 没钱了,勿取

  1. NSLock 类还增加了 tryLocklockBeforeDate: 方法
1
2
- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;
  • tryLock 尝试加锁,它不会阻塞线程,失败返回NO。
  • lockBeforeDate: 在指定Date之前尝试加锁,如果在指定时间之前都不能加锁,则返回NO。
    示例代码:
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
- (void)nslockTest2 {
_lock = [[NSLock alloc] init];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[_lock lock];
NSLog(@"线程1同步开始");
sleep(5);
NSLog(@"线程1同步结束");
[_lock unlock];
});

dispatch_async(dispatch_get_global_queue(0, 0), ^{
sleep(1);
NSLog(@"尝试加锁");
if ([_lock tryLock]) {//尝试加锁,如果不行返回NO,不会阻塞该线程
NSLog(@"加锁成功");
[_lock unlock];
} else {
NSLog(@"加锁失败,已经有锁了");
}
NSDate *date = [[NSDate alloc] initWithTimeIntervalSinceNow:3];
NSLog(@"3s后尝试加锁");
if ([_lock lockBeforeDate:date]) {//尝试在未来的3s内加锁,并阻塞该线程,如果3s内不行恢复线程, 返回NO,不会阻塞该线程
NSLog(@"没有超时,加锁成功");
[_lock unlock];
} else {
NSLog(@"超时,加锁失败");
}
NSLog(@"线程2 OK");
});
}

输出结果:

1
2
3
4
5
6
7
2018-07-09 16:33:51.939314+0800 YSThreadSafetyTest[3874:1773831] 线程1同步开始
2018-07-09 16:33:52.944446+0800 YSThreadSafetyTest[3874:1773854] 尝试加锁
2018-07-09 16:33:52.944671+0800 YSThreadSafetyTest[3874:1773854] 加锁失败,已经有锁了
2018-07-09 16:33:52.944826+0800 YSThreadSafetyTest[3874:1773854] 3s后尝试加锁
2018-07-09 16:33:55.946002+0800 YSThreadSafetyTest[3874:1773854] 超时,加锁失败
2018-07-09 16:33:55.946176+0800 YSThreadSafetyTest[3874:1773854] 线程2 OK
2018-07-09 16:33:56.944367+0800 YSThreadSafetyTest[3874:1773831] 线程1同步结束

NSRecursiveLock 递归锁

有时候“加锁代码”中存在递归调用,递归开始前加锁,递归调用开始后会重复执行此方法以至于反复执行加锁代码最终造成死锁。

如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- (void)recursiveLockTest {
_lock = [[NSLock alloc] init];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"递归开始");
static void(^TestMethod)(int);
TestMethod = ^(int value){
[_lock lock];
if (value > 0) {
sleep(1);
value--;
NSLog(@"递归中 %zd", value);
TestMethod(value);
}
[_lock unlock];
};
TestMethod(5);
NSLog(@"结束");
});
}

输出结果:

1
2
2018-07-09 16:39:52.833302+0800 YSThreadSafetyTest[3947:1804640] 递归开始
2018-07-09 16:39:53.834239+0800 YSThreadSafetyTest[3947:1804640] 递归中 4

可以看到永远都不会结束,这是一个死锁情况。在这个线程中,TestMethod 递归调用,每次进入Block 都会去加一次锁,而从第二次开始,由于锁已经使用了且没有解锁,所以他需要等待锁被解除,这样就导致死锁,线程被阻塞住。

这里可以使用 NSRecursiveLock 递归锁来解决。递归锁可以在一个线程中反复获取锁而不造成死锁,这个过程中会记录获取锁和释放锁的次数,只有最后两者平衡锁才被最终释放。

NSConditionLock 条件锁

NSCoditionLock 做多线程之间的任务等待调用,而且是线程安全的。

NSConditionLock 源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@interface NSConditionLock : NSObject <NSLocking> {
@private
void *_priv;
}

- (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER;

@property (readonly) NSInteger condition;
- (void)lockWhenCondition:(NSInteger)condition;
- (BOOL)tryLock;
- (BOOL)tryLockWhenCondition:(NSInteger)condition;
- (void)unlockWithCondition:(NSInteger)condition;
- (BOOL)lockBeforeDate:(NSDate *)limit;
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;

@property (nullable, copy) NSString *name NS_AVAILABLE(10_5, 2_0);

@end

NSConditionLockNSLock 类似,都遵循 NSLocking 协议,方法都类似,只是多了一个 condition 属性,以及每个操作都多了一个关于 condition 属性的方法,例如 tryLocktryLockWhenCondition:NSConditionLock 可以称为条件锁,只有 condition 参数与初始化时候的 condition 相等,lock 才能正确进行加锁操作。而 unlockWithCondition: 并不是当 condition符合条件时才解锁,而是解锁之后,修改 Condition 的值。

如下代码:

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
- (void)conditionLockTest {
NSMutableArray *products = [NSMutableArray array];
NSInteger HAS_DATA = 1;
NSInteger NO_DATA = 0;
_conditionLock = [[NSConditionLock alloc] initWithCondition:NO_DATA]; // 初始化一个条件

dispatch_async(dispatch_get_global_queue(0, 0), ^{
while (1) {
[_conditionLock lockWhenCondition:NO_DATA]; // 当条件为NO_DATA时加锁
[products addObject:[[NSObject alloc] init]];
NSLog(@"生产");
[_conditionLock unlockWithCondition:HAS_DATA]; // 解锁并将条件置为HAS_DATA
sleep(5);
}
});

dispatch_async(dispatch_get_global_queue(0, 0), ^{
while (1) {
NSLog(@"等待");
[_conditionLock lockWhenCondition:HAS_DATA]; // 当条件为HAS_DATA时加锁
[products removeObjectAtIndex:0];
NSLog(@"售卖");
[_conditionLock unlockWithCondition:NO_DATA]; // 解锁并将条件置为NO_DATA
}
});
}

输出结果:

1
2
3
4
5
2018-07-09 16:55:54.080428+0800 YSThreadSafetyTest[4077:1872496] 等待
2018-07-09 16:55:54.080425+0800 YSThreadSafetyTest[4077:1872434] 生产
2018-07-09 16:55:54.080664+0800 YSThreadSafetyTest[4077:1872496] 售卖
2018-07-09 16:55:54.080751+0800 YSThreadSafetyTest[4077:1872496] 等待
...

NSCondition

一种最基本的条件锁。手动控制线程wait和signal。

NSCondition 源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
@interface NSCondition : NSObject <NSLocking> {
@private
void *_priv;
}

- (void)wait;
- (BOOL)waitUntilDate:(NSDate *)limit;
- (void)signal;
- (void)broadcast;

@property (nullable, copy) NSString *name NS_AVAILABLE(10_5, 2_0);

@end

NSCondition 的对象实际上作为一个锁和一个线程检查器,锁上之后其它线程也能上锁,而之后可以根据条件决定是否继续运行线程,即线程是否要进入 waiting 状态,经测试,NSCondition 并不会像上文的那些锁一样,先轮询,而是直接进入 waiting 状态,当其它线程中的该锁执行 signal 或者 broadcast 方法时,线程被唤醒,继续运行之后的方法。

示例代码:

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
- (void)conditionTest {
NSCondition *condition = [[NSCondition alloc] init];
NSMutableArray *products = [NSMutableArray array];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
while (1) {
[condition lock];
if ([products count] == 0) {
NSLog(@"等待添加");
[condition wait]; // waitUntilDate: 方法有个等待时间限制,指定的时间到了,则放回 NO,继续运行接下来的任务
}
[products removeObjectAtIndex:0];
NSLog(@"删除第一个");
[condition unlock];
}
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
while (1) {
[condition lock];
[products addObject:[[NSObject alloc] init]];
NSLog(@"数组 总量:%zi",products.count);
[condition signal];
[condition unlock];
sleep(1);
}
});
}

输出结果:

1
2
3
4
2018-07-09 17:08:52.504797+0800 YSThreadSafetyTest[4144:1951113] 等待添加
2018-07-09 17:08:52.504920+0800 YSThreadSafetyTest[4144:1951112] 数组 总量:1
2018-07-09 17:08:52.505196+0800 YSThreadSafetyTest[4144:1951113] 删除第一个
...

  • waitUntilDate: 方法有个等待时间限制,指定的时间到了,则放回 NO,继续运行接下来的任务
  • 其中 signalbroadcast 方法的区别在于,signal 只是一个信号量,只能唤醒一个等待的线程,想唤醒多个就得多次调用,而 broadcast 可以唤醒所有在等待的线程。如果没有等待的线程,这两个方法都没有作用。

pthread_mutex 互斥锁(C语言)

c语言定义下多线程加锁方式。

  1. pthread_mutex_init(pthread_mutex_t mutex,const pthread_mutexattr_t attr); 初始化锁变量mutex。attr为锁属性,NULL值为默认属性。
  2. pthread_mutex_lock(pthread_mutex_t mutex); 加锁
  3. pthread_mutex_tylock(*pthread_mutex_t *mutex); 加锁,但是与2不一样的是当锁已经在使用的时候,返回为EBUSY,而不是挂起等待。
  4. pthread_mutex_unlock(pthread_mutex_t *mutex); 释放锁
  5. pthread_mutex_destroy(pthread_mutex_t* mutex); 使用完后释放

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#import <pthread.h>
- (void)pthread_mutex_test {
__block pthread_mutex_t theLock;
pthread_mutex_init(&theLock, NULL);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
pthread_mutex_lock(&theLock);
NSLog(@"线程1同步开始");
sleep(3);
NSLog(@"线程1同步结束");
pthread_mutex_unlock(&theLock);
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);
pthread_mutex_lock(&theLock);
NSLog(@"线程2同步操作");
pthread_mutex_unlock(&theLock);
});
}

输出结果:

1
2
3
2018-07-09 17:18:39.172501+0800 YSThreadSafetyTest[4201:2003796] 线程1同步开始
2018-07-09 17:18:42.177652+0800 YSThreadSafetyTest[4201:2003796] 线程1同步结束
2018-07-09 17:18:42.177872+0800 YSThreadSafetyTest[4201:2004380] 线程2同步操作

  • pthread_mutex 还可以创建条件锁,提供了和 NSCondition 一样的条件控制,初始化互斥锁同时使用 pthread_cond_init 来初始化条件数据结构
1
2
3
4
5
6
7
8
9
10
11
12
// 初始化
int pthread_cond_init (pthread_cond_t *cond, pthread_condattr_t *attr);
// 等待(会阻塞)
int pthread_cond_wait (pthread_cond_t *cond, pthread_mutex_t *mut);
// 定时等待
int pthread_cond_timedwait (pthread_cond_t *cond, pthread_mutex_t *mut, const struct timespec *abstime);
// 唤醒
int pthread_cond_signal (pthread_cond_t *cond);
// 广播唤醒
int pthread_cond_broadcast (pthread_cond_t *cond);
// 销毁
int pthread_cond_destroy (pthread_cond_t *cond);

pthread_mutex 还提供了很多函数,有一套完整的API,包含 Pthreads线程的创建控制等等,非常底层,可以手动处理线程的各个状态的转换即管理生命周期,甚至可以实现一套自己的多线程,感兴趣的可以继续深入了解。

dispatch_semaphore 信号量实现加锁(GCD)

dispatch_semaphore_t GCD 中信号量,也可以解决资源抢占问题,支持信号通知和信号等待。每当发送一个信号通知,则信号量 +1;每当发送一个等待信号时信号量 -1,;如果信号量为 0 则信号会处于等待状态,直到信号量大于 0(或者超时) 开始执行之后代码。

源码如下:

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
/*!
* @param value
* 信号量的起始值,当传入的值小于零时返回NULL
* @result
* 成功返回一个新的信号量,失败返回NULL
*/
dispatch_semaphore_t dispatch_semaphore_create(long value)

/*!
* @discussion
* 信号量减1,如果结果小于0,那么等待队列中信号增量到来直到timeout
* @param dsema
* 信号量
* @param timeout
* 等待时间
* 类型为dispatch_time_t,这里有两个宏DISPATCH_TIME_NOW、DISPATCH_TIME_FOREVER
* @result
* 若等待成功返回0,timeout返回非0
*/
long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);

/*!
* @discussion
* 信号量加1,如果之前的信号量小于0,将唤醒一条等待线程
* @param dsema
* 信号量
* @result
* 唤醒一条线程返回非0,否则返回0
*/
long dispatch_semaphore_signal(dispatch_semaphore_t dsema)

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- (void)semaphoreTest {
// 创建信号量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC);

//线程1
dispatch_async(dispatch_get_global_queue(0, 0), ^{
dispatch_semaphore_wait(semaphore, overTime); // DISPATCH_TIME_FOREVER
NSLog(@"任务1开始");
sleep(3);
NSLog(@"任务1结束");
dispatch_semaphore_signal(semaphore);
});

//线程2
dispatch_async(dispatch_get_global_queue(0, 0), ^{
sleep(1);
dispatch_semaphore_wait(semaphore, overTime);
NSLog(@"任务2");
dispatch_semaphore_signal(semaphore);
});
}

输出结果:

1
2
3
2018-07-09 17:30:16.753036+0800 YSThreadSafetyTest[4309:2092729] 任务1开始
2018-07-09 17:30:19.755907+0800 YSThreadSafetyTest[4309:2092729] 任务1结束
2018-07-09 17:30:19.755907+0800 YSThreadSafetyTest[4309:2092656] 任务2

将超时代码设置为1,则输出:

1
2
3
2018-07-09 17:28:41.082817+0800 YSThreadSafetyTest[4286:2081073] 任务1开始
2018-07-09 17:28:42.085262+0800 YSThreadSafetyTest[4286:2081075] 任务2
2018-07-09 17:28:44.084425+0800 YSThreadSafetyTest[4286:2081073] 任务1结束

OSSpinLock 自旋锁

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#import <libkern/OSAtomic.h>

- (void)osspinlockTest {
__block OSSpinLock theLock = OS_SPINLOCK_INIT;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
OSSpinLockLock(&theLock);
NSLog(@"线程1同步操作开始");
sleep(3);
NSLog(@"线程1同步操作结束");
OSSpinLockUnlock(&theLock);
});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
OSSpinLockLock(&theLock);
sleep(1);
NSLog(@"线程2同步操作");
OSSpinLockUnlock(&theLock);
});
}

输出结果:

1
2
3
2018-07-09 17:33:04.759875+0800 YSThreadSafetyTest[4349:2110976] 线程1同步操作开始
2018-07-09 17:33:07.762309+0800 YSThreadSafetyTest[4349:2110976] 线程1同步操作结束
2018-07-09 17:33:08.817638+0800 YSThreadSafetyTest[4349:2110977] 线程2同步操作

OSSpinLock 自旋锁,性能最高的锁。它的缺点是当等待时会消耗大量 CPU 资源,不太适用于较长时间的任务。 YY大神在博客 不再安全的 OSSpinLock 中说明了OSSpinLock 已经不再安全,暂不建议使用。

iOS 10 之后,苹果给出了解决方案,就是用 os_unfair_lock 代替 OSSpinLock

1
'OSSpinLockLock' is deprecated: first deprecated in iOS 10.0 - Use os_unfair_lock_lock() from <os/lock.h> instead

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#import <os/lock.h>

__block os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
dispatch_async(dispatch_get_global_queue(0, 0), ^{
os_unfair_lock_lock(&lock);
NSLog(@"线程1同步操作开始");
sleep(8);
NSLog(@"线程1同步操作结束");
os_unfair_lock_unlock(&lock);
});

dispatch_async(dispatch_get_global_queue(0, 0), ^{
sleep(1);
os_unfair_lock_lock(&lock);
NSLog(@"线程2同步操作");
os_unfair_lock_unlock(&lock);
});

输出结果:

1
2
3
2018-07-09 17:33:04.759875+0800 YSThreadSafetyTest[4349:2110976] 线程1同步操作开始
2018-07-09 17:33:07.762309+0800 YSThreadSafetyTest[4349:2110976] 线程1同步操作结束
2018-07-09 17:33:08.817638+0800 YSThreadSafetyTest[4349:2110977] 线程2同步操作

总结

  • @synchronized:适用线程不多,任务量不大的多线程加锁
  • NSLock:性能不算差,但感觉用的人不多。
  • dispatch_semaphore_t:使用信号来做加锁,性能很高和 OSSpinLock 差不多。推荐
  • NSConditionLock:多线程处理不同任务的通信建议时用, 只加锁的话性能很低。
  • NSRecursiveLock:性能不错,使用场景限制于递归。
  • POSIX(pthread_mutex):C语言的底层api,复杂的多线程处理建议使用,也可以封装自己的多线程。
  • OSSpinLock:性能非常高,可惜不安全了,使用 os_unfair_lock 来代替。


-------------The End-------------