Objective-C线程安全计数器

我正在努力保持networking活动指标在一个线程安全的方式控制。

这是我现在正在做的方式,但我认为必须有一个更好的方法来做到这一点。 我正在寻找使用锁,但它似乎是一个昂贵的操作。 我一直在看OSAtomicAdd,但不能确切地找出如何在这种情况下使用它。

+ (void)start { [self counterChange:1]; } + (void)stop { [self counterChange:-1]; } + (void)counterChange:(NSUInteger)change { static NSUInteger counter = 0; static dispatch_queue_t queue; if (!queue) { queue = dispatch_queue_create("NetworkActivityIndicator Queue", NULL); } dispatch_sync(queue, ^{ if (counter + change <= 0) { counter = 0; [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; } else { counter += change; [UIApplication sharedApplication].networkActivityIndicatorVisible = YES; } }); } 

怎么能这样做使用OSAtomicAdd?

你不能单靠像OSAtomicAdd这样的东西来同步这种操作。 整个操作需要被locking,以确保它成功的工作。

考虑这个答案中提出的解决scheme,基本上归结为:

 static volatile int32_t NumberOfCallsToSetVisible = 0; int32_t newValue = OSAtomicAdd32((setVisible ? +1 : -1), &NumberOfCallsToSetVisible); [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:(newValue > 0)]; 

如果这个代码是从一个线程调用的, setVisible设置为YES ,那么对OSAtomicAdd32的调用OSAtomicAdd32 NumberOfCallsToSetVisible加1,导致newValue被设置为1。

现在考虑如果在下一行执行之前该线程被抢占会发生什么情况,另一个线程调用setVisible设置为NO的函数。 这次对OSAtomicAdd32的调用将从NumberOfCallsToSetVisible减1,导致newValue被设置为0。

如果第二个线程继续,并且下一行被执行, newValue不会大于零,所以setNetworkActivityIndicatorVisible方法将被调用NO 。 在这一点上,活动指标无论如何都是不可见的,所以这没有做什么,但也没有任何伤害。

但是,最终我们将切换回newValue设置为1的第一个线程。因此,当该线程执行下一行时, newValue明显大于零,并且setNetworkActivityIndicatorVisible方法将被调用YES ,从而使活动指示器可见。

所以我们在setVisible设置为YES调用函数一次, setVisible值设置为NO 。 你会期望这会导致活动指标不可见,但这不是发生了什么事情。 事实上,如果没有其他的电话,它将永远保持可见。 这显然是不对的。

底线是你需要把整个东西包装在一个@synchronize块或类似的东西里。

我将推荐使用OSAtomicAdd32 ,而不是OSAtomicAdd32函数。

 + (void)counterChange:(NSUInteger)change { static int32_t counter = 0; int32_t localCounter, newCounter; do { localCounter = counter; newCounter = localCounter + change; newCounter = newCounter <= 0 ? 0 : newCounter; } while (!OSAtomicCompareAndSwap32(localCounter, newCounter, &counter)); [UIApplication sharedApplication].networkActivityIndicatorVisible = counter > 0; } 

该函数将比较localCountercounter的当前值,只有当它们匹配时才会改变counternewCounter ,所有的都是primefaces的。 如果当前线程中的其他线程改变了localCounterlocalCounter的调用,那么检查将失败,并且将会重试。

即使看起来它可能会留下一些线程永远循环,这种结构是足够安全的在现实世界的条件。

NSLock (适用于iOS 2.0+和OS X 10.0+)是您正在寻找的。

一个NSLock对象用于协调同一个应用程序中多个执行线程的操作。 NSLock对象可用于调解对应用程序全局数据的访问,或保护代码的关键部分,使其能够以primefaces方式运行。

您可以初始化您的应用程序委托中的锁,并在计数器代码周围调用-lock-unlock

 // Assuming the application delegate implements -counterChangeLock // to return a momoized instance of NSLock + (NSLock *)counterChangeLock { return [(AppDelegate *)([UIApplication sharedApplication].delegate) counterChangeLock]; } + (void)start { [[self counterChangeLock] lock]; // blocks if counter is locked already // safely increment counter [[self counterChangeLock] unlock]; } + (void)stop { [[self counterChangeLock] lock]; // safely decrement counter [[self counterChangeLock] unlock]; } 

我不确定为什么所有这些primefaces操作都被使用,当它复杂化的问题,并没有解决的事实,我们需要修复线程同步,当告诉UIApplication我们需要的数量。

使用@synchronized的build议是正确的解决scheme,因为它提供了一个互斥体,用于递增和调用UIApplication。 如果你的基准@synchronized,你会看到它惊人的速度,这种事情是罕见的,primefacesvariables&比较和交换是错误倾向和不必要的。 不这样做的唯一原因是如果(自我)在许多其他部分同步,在这种情况下,您可以为此保留一个NSObject或使用NSLock和等价物。

从而:

 + (void) incrementActivityCounter { [self changeActivityCounter:1]; } + (void) decrementActivityCounter { [self changeActivityCounter:-1]; } + (void) changeActivityCounter:(int)change { static int counter = 0; @synchronized(self) { counter += change; [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:counter > 0]; } } 

UIApplication的networkActivityIndi​​catorVisible是一个非primefaces属性,所以它只能在主线程中使用。 因此,不需要同步一个计数器,因为它不应该从一个线程被调用。 一个简单的静态int和在停止开始和递减的增量是所有需要的。