在Objective-C中如何@synchronized锁定/解锁?


201

@synchronized是否不使用“锁定”和“解锁”来实现互斥?那么它如何锁定/解锁?

以下程序的输出仅是“ Hello World”。

@interface MyLock: NSLock<NSLocking>
@end

@implementation MyLock

- (id)init {
    return [super init];
}

- (void)lock {
    NSLog(@"before lock");
    [super lock];
    NSLog(@"after lock");
}

- (void)unlock {
    NSLog(@"before unlock");
    [super unlock];
    NSLog(@"after unlock");
}

@end


int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    MyLock *lock = [[MyLock new] autorelease];
    @synchronized(lock) {
        NSLog(@"Hello World");
    }

    [pool drain];
}


10
如果不需要,则不需要覆盖init。如果您不重写方法,则运行时将自动调用超类的实现。
君士坦丁·萨鲁哈斯

3
需要注意的重要一点是上述代码未同步。该lock对象在每次调用时都会创建,因此永远不会出现一个@synchronized块锁定另一个块的情况。这意味着没有相互排斥。)当然,上面的示例在中进行了操作main,因此无论如何都没有要排斥的东西,但是不应盲目地将该代码复制到其他地方。
2015年

3
阅读此SO页面后,我决定对@synchronized进行更彻底的调查,并在其上撰写博客文章。您可能会发现它很有用:rykap.com/objective-c/2015/05/09/synchronized
rjkaplan

Answers:


323

就像这样,Objective-C语言级同步使用互斥量NSLock。从语义上讲,存在一些小的技术差异,但是将它们视为在一个共同的(更原始的)实体之上实现的两个单独的接口,基本上是正确的。

特别是对于a,NSLock您具有显式锁,而对于与@synchronized您要同步的对象相关联的隐式锁。语言级别锁定的好处是编译器可以理解它,因此它可以处理范围界定问题,但是从机械上讲,它们的行为基本上相同。

您可以将其@synchronized视为编译器重写:

- (NSString *)myString {
  @synchronized(self) {
    return [[myString retain] autorelease];
  }
}

转换为:

- (NSString *)myString {
  NSString *retval = nil;
  pthread_mutex_t *self_mutex = LOOK_UP_MUTEX(self);
  pthread_mutex_lock(self_mutex);
  retval = [[myString retain] autorelease];
  pthread_mutex_unlock(self_mutex);
  return retval;
}

这并不完全正确,因为实际转换更加复杂并且使用了递归锁,但是应该可以理解这一点。


17
您还忘记了@synchronized为您执行的异常处理。据我了解,其中大部分是在运行时处理的。这样就可以优化对非竞争锁等
奎因·泰勒

7
就像我说的那样,实际生成的内容更加复杂,但是我不希望编写节指令来构建DWARF3展开表;-)
Louis Gerbarg

而且我不能怪你。:-)另外请注意,OS X使用Mach-O格式而不是DWARF。
奎因·泰勒,

5
没有人使用DWARF作为二进制格式。OS X确实将DWARF用于调试符号,并且将DWARF展开表用于零成本例外
Louis Gerbarg,2009年

7
作为参考,我为Mac OS X编写了编译器后端;-)
Louis Gerbarg,2009年

40

在Objective-C中,@synchronized块自动为您处理锁定和解锁(以及可能的异常)。运行时实质上会动态生成一个NSRecursiveLock,该对象与您正在同步的对象相关联。此Apple文档对此进行了更详细的说明。这就是为什么您看不到NSLock子类的日志消息的原因-同步的对象可以是任何东西,而不仅仅是NSLock。

基本上,这@synchronized (...)是一种简化代码的便捷构造。像大多数简化的抽象一样,它也具有相关的开销(将其视为隐藏的成本),意识到这一点很好,但是无论如何使用这种结构时,原始性能可能并不是最高的目标。



31

其实

{
  @synchronized(self) {
    return [[myString retain] autorelease];
  }
}

直接转换为:

// needs #import <objc/objc-sync.h>
{
  objc_sync_enter(self)
    id retVal = [[myString retain] autorelease];
  objc_sync_exit(self);
  return retVal;
}

此API自iOS 2.0起可用,并使用...导入。

#import <objc/objc-sync.h>

因此,它不提供对干净处理抛出异常的支持吗?
达斯汀

这是在某处记录的吗?
jbat100

6
那里的支撑不平衡。
Potatoswatter

@Dustin实际上从文档中做到了这一点:“作为一种预防措施,该@synchronized块会在受保护的代码中隐式添加一个异常处理程序。该处理程序在引发异常的情况下会自动释放互斥体。”
Pieter 2013年

objc_sync_enter可能会使用pthread互斥锁,因此Louis的转换更加深入和正确。
杰克


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.