我定义了一个NSInteger counter
并在回调中更新了它的值,如下面的代码所示(回调在另一个线程中):
-(void) myFunc {
NSLog(@"initialise counter...");
// I try to use volatile to make it thread safe
__block volatile NSInteger counter = 0;
[self addObserver:myObserver withCallback:^{
// this is in another thread
counter += 1;
NSLog(@"counter = %d", counter);
}];
}
我使用volatile
关键字使counter
线程安全,在一个属于另一个线程的回调块中访问它。
当我两次调用myFunc
时:
// 1st time call
[self myFunc];
// 2nd time call
[self myFunc];
输出如下:
initialise counter...
counter = 1;
counter = 2;
counter = 3;
counter = 4;
counter = 1; // weird
initialise counter...
counter = 2; // weird
counter = 3;
counter = 1; // weird
counter = 4;
看起来第二次调用会生成一个初始值错误的计数器,counter=4
之前的输出为counter=1
,这也很奇怪。
是否因为我的代码即使使用volatile
关键字也不是线程安全的?如果是这样,如何使我的计数器线程安全?如果它是线程安全的,为什么我会得到奇怪的输出?
答案 0 :(得分:4)
对于原子计数器的简单情况,GCD是过度的。使用OSAtomic功能:
-(void) myFunc {
static int64_t counter;
[self addObserver:myObserver withCallback:^{
// this is in another thread
int64_t my_value = OSAtomicIncrement64Barrier(&counter);
NSLog(@"counter = %d", my_value);
}];
}
请注意,代码记录增量函数的结果而不是静态变量。结果为您提供特定操作的原子结果。使用静态变量将为您提供计数器的快照,该计数器与增量操作相比不是原子的。
答案 1 :(得分:0)
您的代码有很多问题。看起来你反复调用myFunc。每次执行时,都会创建一个新的计数器实例。
使您的计数器成为实例变量或应用程序范围的全局。
使计数器线程安全递增(和记录)的一种简单方法是使观察者的主体使用dispatch_async(dispatch_get_main_queue()<your code here>)
。这样,与计数器混淆的代码总是在主线程上运行,即使它是从其他线程调用的。这不是处理它的最佳方式,但它很容易。
否则,您将需要使用锁或其他一些并发技术。这需要对线程安全有一个很好的理解,坦率地说,你的帖子表明你没有。 (不是说,它是计算中比较困难的主题之一。)
正如Avi在评论中指出的那样,使用主队列来管理计数器会导致其他线程阻塞主线程的等待,并且不是一个非常好的解决方案。 (它会工作,但会带走使用多个线程的所有性能优势)
最好设置一个串行队列,并使其成为管理此计数器的对象的延迟加载属性,并使用dispatch_once()
进行保护。但是,我没有足够的咖啡在论坛帖子中写出这些代码。
答案 2 :(得分:0)
首先使用局部变量已损坏。当函数返回时,它将从堆栈中删除。因此,块执行块定义(计数器= 0)时复制变量的值(捕获)并在副本上工作。
如果你有一个共享资源作为计数器,你必须将访问权限放入一个块。
// global
dispatch_queue_t counterQueue;
int counter;
// initialize
counterQueue = dispatch_queue_create( "com.yourname.counterQueue", DISPATCH_QUEUE_SERIAL);
counter = 0;
// Whenever you read or write to counter
dispatch_async( counterQueue,
^{
counter++;
NSLog( @"%d", counter" );
}
// or
int lastValue;
dispatch_sync( counterQueue,
^{
lastValue = counter;
}
// Do something with it.