静态变量可以用作@synchronized 参数吗?

Can a static variable used as @synchronized parameter?

我们想保证静态变量的线程安全。 我们在@synchronized 指令中使用了另一个静态变量作为对象。像这样:

static NSString *_saveInProgressLock = @"SaveInProgressLock";
static BOOL _saveInProgress;

+ (BOOL)saveInProgress {
    @synchronized(_saveInProgressLock) {
        return _saveInProgress;
    }
}

+ (void)setSaveInProgress:(BOOL)save {
    @synchronized(_saveInProgressLock) {
        _saveInProgress = save;
    }
}

我们在商店中当前遇到的应用程序问题,可以通过阻止将 _saveInProgress 变量设置为 NO 来重现。 你看上面的代码有什么问题吗?

它和这个有什么不同?

static BOOL _saveInProgress;

+ (BOOL)saveInProgress {
    @synchronized([MyClass class]) {
        return _saveInProgress;
    }
}

+ (void)setSaveInProgress:(BOOL)save {
    @synchronized([MyClass class]) {
        _saveInProgress = save;
    }
}

tl;dr: 只要字符串文字是唯一的,这是绝对安全的。如果它不是唯一的,则可能存在(良性)问题,但通常仅在发布模式下存在。不过,可能有更简单的方法来实现这一点。


@synchronized 块是使用运行时函数 objc_sync_enterobjc_sync_exit (source) 实现的。这些函数是使用全局(但 objc 内部)侧 table 锁实现的,该锁由指针值键入。在 C-API 级别,您还可以锁定 (void *)42,或者实际上是任何指针值。对象是否存在甚至都没有关系,因为指针永远不会被取消引用。但是,如果 obj 没有静态类型检查为 id 类型(其中 NSString * 是一个子类型,所以没关系),objc 编译器拒绝编译 @synchronized(obj) 表达式也许它保留了对象(我不确定),所以你应该只将它与对象一起使用。

不过有两个关键点需要考虑:

  • 如果您同步的 obj 是 NULL 指针(Objective C 中的 nil),则 objc_sync_enterobjc_sync_exit 不是- ops,这会导致在完全没有锁定的情况下执行块的不良情况。
  • 如果您对不同的 @synchronized 块使用相同的字符串值,编译器可能足够聪明,可以将它们映射到相同的指针地址。也许编译器现在不这样做,但它是 Apple 将来可能引入的完全有效的优化。因此,您应该确保使用唯一的名称。如果发生这种情况,两个不同的 @synchronized 块可能会在程序员想要使用不同锁的地方意外地使用同一个锁。对了,也可以用[NSObject new]作为锁对象。

在 class 对象 ([MyClass class]) 上同步也是非常安全的。


现在介绍更简单的方法。如果你只有一个 BOOL 变量,你想成为原子变量,你可以使用无锁编程:

static BOOL _saveInProgress;

+ (BOOL)saveInProgress {
    __sync_synchronize();
    return _saveInProgress;
}

+ (void)setSaveInProgress:(BOOL)save {
    _saveInProgress = save;
    __sync_synchronize();
}

这具有更好的性能并且同样是线程安全的。 __sync_synchronize() 是内存障碍。


但是请注意,这两种解决方案的安全性取决于您如何使用它们。如果你有一个看起来像这样的保存方法:

+ (void)save { // line 21
    if(![self saveInProgress]) { // line 22
        [self setSaveInProgress:YES]; // line 23
        // ... do stuff ...
        [self setSaveInProgress:NO]; // line 40
    }
}

+save 方法根本不是线程安全的,因为第 22 行和第 23 行之间存在竞争条件。(不想在这里详细说明.. 如果需要,请提出一个新问题更多信息。)