使一个简单的NSInteger计数器线程安全

时间:2016-06-23 11:26:32

标签: ios objective-c multithreading

我定义了一个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关键字也不是线程安全的?如果是这样,如何使我的计数器线程安全?如果它是线程安全的,为什么我会得到奇怪的输出?

3 个答案:

答案 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.
相关问题