【问题标题】:Can a static variable used as @synchronized parameter?可以将静态变量用作@synchronized 参数吗?
【发布时间】:2018-08-31 08:44:27
【问题描述】:

我们希望保证静态变量的线程安全。 我们在@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;
    }
}

【问题讨论】:

    标签: objective-c multithreading static thread-safety synchronized


    【解决方案1】:

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


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

    但有两个关键点需要考虑:

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

    在类对象 ([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 行之间存在竞争条件。(不想在这里详细说明.. 如果您需要更多信息,请提出一个新问题。)

    【讨论】:

    • 感谢非常详细的解释!
    猜你喜欢
    • 1970-01-01
    • 2013-09-15
    • 1970-01-01
    • 2018-11-02
    • 1970-01-01
    • 2010-09-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多