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

Objective c 静态变量可以用作@synchronized参数吗?,objective-c,multithreading,static,thread-safety,synchronized,Objective C,Multithreading,Static,Thread Safety,Synchronized,我们想保证静态变量的线程安全性。 我们在@synchronized指令中使用了另一个静态变量作为对象。像这样: static NSString *_saveInProgressLock = @"SaveInProgressLock"; static BOOL _saveInProgress; + (BOOL)saveInProgress { @synchronized(_saveInProgressLock) { return _saveInProgress;

我们想保证静态变量的线程安全性。 我们在@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\u sync\u enter
objc\u sync\u exit
()实现。这些函数是使用全局(但objc内部)锁边表实现的,锁边表由指针值键控。在C-API级别上,您还可以锁定
(void*)42
,或者实际上锁定任何指针值。对象是否活动都无关紧要,因为指针从未被解除引用。但是,如果
obj
没有对
id
类型进行静态类型检查(其中
NSString*
是一个子类型,所以它是可以的),那么objc编译器拒绝编译
@synchronized(obj)
表达式,并且可能它保留了对象(我不确定),因此您应该只对对象使用它

有两个关键点需要考虑:

  • 如果进行同步的
    obj
    是空指针(
    nil
    ,在目标C中),则
    objc\u sync\u enter
    objc\u sync\u exit
    都不是操作,这会导致执行块时绝对没有锁定的不良情况
  • 如果对不同的
    @synchronized
    块使用相同的字符串值,编译器可能足够聪明,可以将它们映射到相同的指针地址。也许编译器现在没有这样做,但这是一个完美有效的优化,苹果可能会在未来引入。因此,您应该确保使用唯一的名称。如果发生这种情况,两个不同的
    @synchronized
    块可能会意外地使用同一个锁,而程序员希望使用不同的锁。顺便说一下,您还可以将
    [NSObject new]
    用作锁对象
在类对象(
[MyClass]
)上同步也是完全安全的


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

static BOOL _saveInProgress;

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

+ (void)setSaveInProgress:(BOOL)save {
    _saveInProgress = save;
    __sync_synchronize();
}
这具有更好的性能,并且是线程安全的<代码>\uuu sync\u 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行之间存在争用条件。(不想在此详述……如果需要更多信息,只需问一个新问题即可。)

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


@synchronized
块使用运行时函数
objc\u sync\u enter
objc\u sync\u exit
()实现。这些函数是使用全局(但objc内部)锁边表实现的,锁边表由指针值键控。在C-API级别上,您还可以锁定
(void*)42
,或者实际上锁定任何指针值。对象是否活动都无关紧要,因为指针从未被解除引用。但是,如果
obj
没有对
id
类型进行静态类型检查(其中
NSString*
是一个子类型,所以它是可以的),那么objc编译器拒绝编译
@synchronized(obj)
表达式,并且可能它保留了对象(我不确定),因此您应该只对对象使用它

有两个关键点需要考虑:

  • 如果进行同步的
    obj
    是空指针(
    nil
    ,在目标C中),则
    objc\u sync\u enter
    objc\u sync\u exit
    都不是操作,这会导致执行块时绝对没有锁定的不良情况
  • 如果对不同的
    @synchronized
    块使用相同的字符串值,编译器可能足够聪明,可以将它们映射到相同的指针地址。也许编译器现在没有这样做,但这是一个完美有效的优化,苹果可能会在未来引入。因此,您应该确保使用唯一的名称。如果发生这种情况,两个不同的
    @synchronized
    块可能会意外地使用同一个锁,而程序员希望使用不同的锁。顺便说一下,您还可以将
    [NSObject new]
    用作锁对象
在类对象(
[MyClass]
)上同步也是完全安全的


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

static BOOL _saveInProgress;

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

+ (void)setSaveInProgress:(BOOL)save {
    _saveInProgress = save;
    __sync_synchronize();
}
这具有更好的性能,并且是线程安全的<代码>\uuu sync\u 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行之间存在争用条件。(不想在这里详细说明。如果需要更多信息,只需问一个新问题即可。)

谢谢您的详细解释!谢谢你的详细解释!