前言
本文从atomic
和 nonatomic
的区别说起,又叙述了一下@synchronized
的作用。
atomic
和 nonatomic
属性表示变量的原子性,nonatomic
表示不保证线程安全,但其实atomic
也不能一定保证线程安全。
具体看一下编译器对atomic
和 nonatomic
做了什么。
nonatomic
如果一个属性为 nonatomic
时,默认的setter
和 getter
如下:
@property(nonatomic, retain) UITextField *userName;
- (UITextField *) userName {
return userName;
}
- (void) setUserName:(UITextField *)userName_ {
[userName_ retain];
[userName release];
userName = userName_;
}
atomic
如果一个属性为 nonatomic
时,默认的setter
和 getter
如下:
@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
,此时的 setter
和 getter
中多了一个 @synchronized(self)
。
@synchronized
的作用是创建一个互斥锁,保证此时没有其它线程对 self 对象进行修改,其实这样子只保证了getter
和setter
存取方法的线程安全,并不能保证整个对象是线程安全的。
比如,线程A对一个变量write后,线程B再对同一个变量write,之后线程A对此变量read,这样子线程A会read到线程B的数据,显然是不安全的。
那么@synchronized
具体又是做什么的?
synchronized
编译器遇到@synchronized block
@synchronized(self) {
// code
}
会转化为如下的代码,
{
objc_sync_enter(self)
// code
objc_sync_exit(self);
}
可以看到,这里有两个函数 objc_sync_enter
, objc_sync_exit
,通过源码objc4-680
来查看这两个函数:
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;
}
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
是一个递归锁,
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)
最终的效果如下:
{
pthread_mutex_t *self_mutex = LOOK_UP_MUTEX(self);
pthread_mutex_lock(self_mutex);
// code
pthread_mutex_unlock(self_mutex);
}
递归锁也意味着如下代码没有问题:
@synchronized (obj) {
NSLog(@"1st sync");
@synchronized (obj) {
NSLog(@"2nd sync");
}
}
小结
- 你调用
sychronized
的每个对象,Objective-C runtime
都会为其分配一个递归锁并存储在哈希表中。 - 如果在
sychronized
内部对象被释放或被设为 nil 看起来都 OK。不过这没在文档中说明,所以我不会再生产代码中依赖这条。 - 注意不要向你的
sychronized block
传入 nil!这将会从代码中移走线程安全。你可以通过在objc_sync_nil
上加断点来查看是否发生了这样的事情。
网友评论