【问题标题】:How does @synchronized lock/unlock in Objective-C?在 Objective-C 中 @synchronized 如何锁定/解锁?
【发布时间】:2010-11-15 23:09:51
【问题描述】:

@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];
}

【问题讨论】:

  • 如果你不需要它,你不需要重写它。如果您不覆盖方法,运行时会自动调用超类的实现。
  • 需要注意的重要一点是上面的代码是不同步的。 lock 对象是在每次调用时创建的,因此永远不会出现一个 @synchronized 块锁定另一个块的情况。并且这意味着没有互斥。)当然,上面的例子是在main中进行操作,所以无论如何也没有什么可以排除的,但不要盲目地将代码复制到其他地方。
  • 在阅读了这个 SO 页面后,我决定更彻底地调查 @synchronized 并写一篇关于它的博客文章。您可能会发现它很有用:rykap.com/objective-c/2015/05/09/synchronized

标签: objective-c synchronization


【解决方案1】:

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

特别是对于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;
}

这并不完全正确,因为实际的转换更复杂并使用递归锁,但它应该明白这一点。

【讨论】:

  • 您还忘记了@synchronized 为您执行的异常处理。据我了解,其中大部分是在运行时处理的。这允许对非竞争锁等进行优化。
  • 就像我说的,实际生成的东西更复杂,但我不想编写部分指令来构建 DWARF3 展开表;-)
  • 我不能怪你。 :-) 另请注意,OS X 使用 Mach-O 格式而不是 DWARF。
  • 没有人使用 DWARF 作为二进制格式。 OS X 确实使用 DWARF 来调试符号,并且它使用 DWARF 展开表来实现零成本异常
  • 作为参考,我已经为 Mac OS X 编写了编译器后端 ;-)
【解决方案2】:

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

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

【讨论】:

【解决方案3】:

其实

{
  @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>

【讨论】:

  • 所以它不支持干净地处理抛出的异常?
  • 这是否记录在某处?
  • 那里有一个不平衡的支架。
  • @Dustin 实际上确实如此,来自文档:“作为预防措施,@synchronized 块隐式地将异常处理程序添加到受保护的代码中。此处理程序会在抛出异常。”
  • objc_sync_enter 可能会使用 pthread 互斥锁,所以 Louis 的变换更深更正确。
【解决方案4】:

Apple 的@synchronized 实现是开源的,可以在here 找到。 Mike ash 写了两篇关于这个主题的非常有趣的帖子:

简而言之,它有一个表,将对象指针(使用它们的内存地址作为键)映射到pthread_mutex_t 锁,这些锁根据需要被锁定和解锁。

【讨论】:

    【解决方案5】:

    它只是将一个信号量与每个对象相关联,并使用它。

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-04-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多