Avn*_*arr 10 singleton objective-c grand-central-dispatch
有多个源调用特定方法,但我想确保它被调用一次(每个对象)
我想使用类似的语法
// method called possibly from multiple places (threads)
-(void)finish
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self _finishOnce]; // should happen once per object
});
}
// should only happen once per object
-(void)_finishOnce{...}
Run Code Online (Sandbox Code Playgroud)
问题是令牌是在同一个类的所有实例中共享的 - 所以不是一个好的解决方案 - 每个对象是否有dispatch_once_t - 如果不是,确保它被调用一次的最佳方法是什么?
编辑:
这是我想到的一个提议的解决方案 - 它看起来好吗?
@interface MyClass;
@property (nonatomic,strong) dispatch_queue_t dispatchOnceSerialQueue; // a serial queue for ordering of query to a ivar
@property (nonatomic) BOOL didRunExactlyOnceToken;
@end
@implementation MyClass
-(void)runExactlyOnceMethod
{
__block BOOL didAlreadyRun = NO;
dispatch_sync(self.dispatchOnceSerialQueue, ^{
didAlreadyRun = _didRunExactlyOnceToken;
if (_didRunExactlyOnceToken == NO) {
_didRunExactlyOnceToken = YES;
}
});
if (didAlreadyRun == YES)
{
return;
}
// do some work once
}
Run Code Online (Sandbox Code Playgroud)
谓词必须指向存储在全局或静态范围内的变量.使用具有自动或动态存储的谓词的结果是未定义的.
该答案中列举了整体关注点.也就是说,它可以使它工作.详细说明:这里的关注点是谓词的存储在初始化时可靠地归零.使用静态/全局语义,这是非常有保证的.现在我知道你在想什么,"......但是,Objective-C对象也会在init上归零!",你一般都是正确的.问题出在哪里是读/写重新排序.某些体系结构(即ARM)在另一个线程尝试读取令牌后具有弱一致性内存模型,这意味着只要保留执行一致性的主要线程的原始意图,就可以重新排序内存读/写.在这种情况下,重新排序可能会让您对"归零"的情况持开放态度.(即-init返回,对象指针变得对另一个线程可见,其他线程尝试访问该令牌,但它仍然是垃圾,因为还没有发生归零操作.)为了避免这个问题,OSMemoryBarrier()到你的-init方法结束,你应该没问题.(请注意,在这里添加内存屏障以及内存屏障一般会有非零性能损失.)内存屏障的细节留作 "进一步阅读"(但如果你要依赖它们,你最好先了解它们,至少在概念上.)
我的猜测是,dispatch_once与非全局/静态存储一起使用的"禁止" 源于无序执行和内存障碍是复杂的主题这一事实,正确的障碍是困难的,弄错它往往导致极其微妙也许最重要的是(虽然我没有根据经验测量),引入所需的内存屏障以确保dispatch_once_t在ivar中的安全使用几乎肯定会否定一些(所有?)性能dispatch_once超过"经典"锁定模式的好处.
另请注意,有两种"重新排序".重新排序是作为编译器优化发生的(这是由volatile关键字影响的重新排序),然后在不同的体系结构上以不同的方式在硬件级别重新排序.这种硬件级重新排序是由内存屏障操纵/控制的重新排序.(即volatile关键字不够用.)
OP特别询问了"完成一次"的方法.在ReactiveCocoa的RACDisposable类中可以看到这样一个模式的一个例子(对我来说看起来是安全/正确的),它在处理时保持零个或一个块运行并保证"一次性"只被处置一次,并且块(如果有的话)只被调用一次.它看起来像这样:
@interface RACDisposable ()
{
void * volatile _disposeBlock;
}
@end
...
@implementation RACDisposable
// <snip>
- (id)init {
self = [super init];
if (self == nil) return nil;
_disposeBlock = (__bridge void *)self;
OSMemoryBarrier();
return self;
}
// <snip>
- (void)dispose {
void (^disposeBlock)(void) = NULL;
while (YES) {
void *blockPtr = _disposeBlock;
if (OSAtomicCompareAndSwapPtrBarrier(blockPtr, NULL, &_disposeBlock)) {
if (blockPtr != (__bridge void *)self) {
disposeBlock = CFBridgingRelease(blockPtr);
}
break;
}
}
if (disposeBlock != nil) disposeBlock();
}
// <snip>
@end
Run Code Online (Sandbox Code Playgroud)
它OSMemoryBarrier()在init中使用,就像你必须使用的一样dispatch_once,然后使用OSAtomicCompareAndSwapPtrBarrier它,顾名思义,暗示一个内存屏障,以原子方式"翻转开关".如果不清楚的话,这里发生的事情是-initivar设定的时间self.这个条件被用作"标记",以区分" 没有阻止但我们没有处置 "和" 有一个阻止但我们已经处置 "的情况.
实际上,如果内存障碍对你来说看起来不透明和神秘,我的建议就是使用经典的锁定模式,直到你测量出那些经典的锁定模式导致应用程序出现真正的,可测量的性能问题.
艾弗纳,你现在可能会后悔当然;-)
关于你对问题的编辑,并考虑到其他问题,你或多或少地重建了"老派"这样做的方式,也许这就是你应该做的事情(代码直接输入,期待拼写错误):
@implemention RACDisposable
{
BOOL ranExactlyOnceMethod;
}
- (id) init
{
...
ranExactlyOnceMethod = NO;
...
}
- (void) runExactlyOnceMethod
{
@synchronized(self) // lock
{
if (!ranExactlyOnceMethod) // not run yet?
{
// do stuff once
ranExactlyOnceMethod = YES;
}
}
}
Run Code Online (Sandbox Code Playgroud)
对此有一个共同的优化,但考虑到其他讨论,让我们跳过它.
这"便宜"吗?可能不是,但所有的东西都是相对的,它的费用可能并不重要 - 但是YMMV!
HTH
| 归档时间: |
|
| 查看次数: |
5592 次 |
| 最近记录: |