Reputation: 42602
I define a NSInteger counter
and updated its value in a callback like the following code shows (callback is in another thread):
-(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);
}];
}
I use volatile
keyword to make the counter
thread safe, it is accessed in a callback block which belongs to another thread.
When I invoke myFunc
two times:
// 1st time call
[self myFunc];
// 2nd time call
[self myFunc];
the output is like this:
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;
It looks like the 2nd time call produce a counter with wrong initial value, and the output before counter=4
is counter=1
which is also weird.
Is it because my code is not thread safe even with volatile
keyword? If so, how to make my counter thread safe? If it is thread safe, why I get weird output?
Upvotes: 4
Views: 867
Reputation: 90561
For the simple case of an atomic counter, GCD is overkill. Use the OSAtomic functions:
-(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);
}];
}
Note that the code logs the result of the increment function rather than the static variable. The result gives you the atomic result of your specific operation. Using the static variable would give you a snapshot of the counter that's not atomic with respect to your increment operation.
Upvotes: 6
Reputation: 131418
There are lots of things wrong with your code. It looks like you're calling myFunc repeatedly. Each time you do, it creates a new instance of the counter.
Make your counter an instance variable or app-wide global.
A simple way to make incrementing (and logging) the counter thread-safe is to make the body of the observer use dispatch_async(dispatch_get_main_queue()<your code here>)
. That way the code that messes with the counter always runs on the main thread, even if it's called from other threads. This isn't the most performant way to handle it, but it's easy.
Otherwise you're going to need to use locks or some other concurrency technique. That requires a strong understanding of thread safety, which your post, frankly, shows that you don't have. (Not to be mean, it's one of the more difficult subjects in computing.)
As Avi points out in his comment, using the main queue to manage the counter would cause the other threads to block waiting on the main thread, and is not a very good solution. (It would work, but would take away just about all the performance benefit of using multiple threads)
It would be better to set up a single serial queue and make that a lazily loaded property of the object that manages this counter, protected with dispatch_once()
. However, I don't have enough coffee on-board to write out that code in a forum post.
Upvotes: 0
Reputation: 16660
First of all using a local variable is corrupted. It will be removed from stack, when the function returns. Therefore the block copies the variable's value (capture) when the block definition is executed (counter = 0) and works on the copy.
If you have a shared resource as the counter is, you have to put accesses to it into a block.
// 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.
Upvotes: 0