从 atomic 说起

前言

本文从atomicnonatomic 的区别说起,又叙述了一下@synchronized的作用。
atomicnonatomic 属性表示变量的原子性,nonatomic 表示不保证线程安全,但其实atomic也不能一定保证线程安全。
具体看一下编译器对atomicnonatomic 做了什么。

nonatomic

如果一个属性为 nonatomic 时,默认的settergetter 如下:

1
2
3
4
5
6
7
8
9
10
11
@property(nonatomic, retain) UITextField *userName;

- (UITextField *) userName {
return userName;
}

- (void) setUserName:(UITextField *)userName_ {
[userName_ retain];
[userName release];
userName = userName_;
}

atomic

如果一个属性为 nonatomic 时,默认的settergetter 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@property(retain) UITextField *userName;

- (UITextField *) userName {
UITextField *retval = nil;
@synchronized(self) {
retval = [[userName retain] autorelease];
}
return retval;
}

- (void) setUserName:(UITextField *)userName_ {
@synchronized(self) {
[userName_ retain];
[userName release];
userName = userName_;
}
}

可以看出相对于 nonatomic ,此时的 settergetter 中多了一个 @synchronized(self)

@synchronized 的作用是创建一个互斥锁,保证此时没有其它线程对 self 对象进行修改,其实这样子只保证了gettersetter存取方法的线程安全,并不能保证整个对象是线程安全的。

比如,线程A对一个变量write后,线程B再对同一个变量write,之后线程A对此变量read,这样子线程A会read到线程B的数据,显然是不安全的。

那么@synchronized 具体又是做什么的?

synchronized

编译器遇到@synchronized block

1
2
3
@synchronized(self) {
// code
}

会转化为如下的代码,

1
2
3
4
5
{
objc_sync_enter(self)
// code
objc_sync_exit(self);
}

可以看到,这里有两个函数 objc_sync_enter , objc_sync_exit,通过源码objc4-680来查看这两个函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int objc_sync_enter(id obj)
{
int result = OBJC_SYNC_SUCCESS;

if (obj) {
SyncData* data = id2data(obj, ACQUIRE);
assert(data);
data->mutex.lock();
} else {
// @synchronized(nil) does nothing
if (DebugNilSync) {
_objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug");
}
objc_sync_nil();
}

return result;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int objc_sync_exit(id obj)
{
int result = OBJC_SYNC_SUCCESS;

if (obj) {
SyncData* data = id2data(obj, RELEASE);
if (!data) {
result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
} else {
bool okay = data->mutex.tryUnlock();
if (!okay) {
result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
}
}
} else {
// @synchronized(nil) does nothing
}


return result;
}

从源码可以看出,有一个互斥锁data->mutex.lock(), data->mutex.tryUnlock() 来保证线程安全。

同时@synchronized(nil)是不起任何作用的。

继续看源码,发现mutex是一个递归锁,

1
2
3
4
5
6
7
8
9
10
11
12
13
typedef struct SyncData {
struct SyncData* nextData;
DisguisedPtr<objc_object> object;
int32_t threadCount; // number of THREADS using this block
recursive_mutex_t mutex;
} SyncData;

using recursive_mutex_t = recursive_mutex_tt<DEBUG>;

class recursive_mutex_tt : nocopy_t {
pthread_mutex_t mLock;
...
}

所以@synchronized(self)最终的效果如下:

1
2
3
4
5
6
{
pthread_mutex_t *self_mutex = LOOK_UP_MUTEX(self);
pthread_mutex_lock(self_mutex);
// code
pthread_mutex_unlock(self_mutex);
}

递归锁也意味着如下代码没有问题:

1
2
3
4
5
6
@synchronized (obj) {
NSLog(@"1st sync");
@synchronized (obj) {
NSLog(@"2nd sync");
}
}

小结

  • 你调用 sychronized 的每个对象,Objective-C runtime 都会为其分配一个递归锁并存储在哈希表中。
  • 如果在 sychronized 内部对象被释放或被设为 nil 看起来都 OK。不过这没在文档中说明,所以我不会再生产代码中依赖这条。
  • 注意不要向你的 sychronized block 传入 nil!这将会从代码中移走线程安全。你可以通过在 objc_sync_nil 上加断点来查看是否发生了这样的事情。

参考