Reputation: 35953
Suppose I have a function like the following one, that is called 30 times per second (this code is a simplification of a more complex code created by Apple that I am trying to understand. The idea of Apple's original code is this: like I said doSomethingWithImage
is called 30 per second. So, the whole method has 1/30 of a second to do everything but imagine that doSomethingThatWillTakeTime
takes more than expected and doSomethingWithImage
is called again, while doSomethingThatWillTakeTime
is already being executed. If this is the case, the code has to drop that image and do nothing.
// code...
@synchronized (self);
[self doSomethingWithImage:image];
}
// ...
// call under @synchronized( self )
- (void)doSomethingWithImage:(UIImage *)image
{
self.myImage = image;
[self executeBlockAsync:^{
UIImage *myImage = nil;
@synchronized( self )
{
myImage = self.myImage;
if (myImage) {
self.myImage = nil; // drop the image
}
}
if ( myImage ) {
[self doSomethingThatWillTakeTime:myImage];
}
}];
}
(void)executeBlockAsync:(dispatch_block_t)block
{
dispatch_async( _myQueue, ^{
callbackBlock();
} );
}
My problem is that looking at this code I cannot see how this is doing that. I do not have a mental image of what lines are being execute in which order for that to occur. Probably I do not understand the role of @synchronized
in doing that, on what exactly is being locked or not.
Upvotes: 1
Views: 101
Reputation: 437482
The only thing that @synchronized
does is to ensure "thread safe", synchronized interaction with some objects or variables. Specifically, it ensures that you don't access these resources from one thread while they're possibly being mutated elsewhere by another thread. If one thread is inside the @synchronized(self) {...}
block and another thread encounters its own @synchronized(self) {...}
block, this latter code won't start running that until the other thread finishes its @synchronized
block.
This is just one of the many synchronization techniques outlined in the Threading Programming Guide: Synchronization. (Or you can also use dedicated GCD queues, either serial queues or concurrent queues that use the reader-writer pattern, to manage this synchronization as outlined in Concurrency Programming Guide: Migrating Away from Threads: Eliminating Lock-based Code. But that's a separate topic altogether.)
In the captureOutput:didOutputSampleBuffer:fromConnection:
method that you shared with us, they're just using @synchronized
to ensure that the _recordingStatus
and the _recorder
don't change in one thread while you're using them from another thread. They accomplish this by making sure that all interactions with these variables (whether reading in one thread or updating in another) happens within their own @synchronized { ... }
blocks.
But note that @synchronized
does not serve any functional purpose beyond that thread safe interaction. All of the logic regarding "should I add a frame or not" is dictated by the simple if
statements in that code. The @synchronized
directives have nothing to do with that. They're just there to ensure thread safety.
Upvotes: 2
Reputation: 114836
It isn't that the code "drops the image and does nothing". The use of the term "drop" is probably a bit misleading; what happens is that images that haven't yet been delivered to the delegate are overwritten if there is a new image.
What happens is something like this:
self.myImage
self.myImage
and then clears self.myImage
doSomethingThatWillTakeTime
Later,
self.myImage
Now say,
self.myImage
- note that this doesn't check to see if self.myImage
is nil, it just overwrites it, so if Image 2 was never processed it has been "dropped"self.myImage
which is now Image 3 and clear self.myImage
doSomethingThatWillTakeTime
nil
from self.myImage
do nothing.So you can see how Image 2 was dropped since it was stale; Image 3 had already arrived, so there was no point in processing Image 2.
Essentially, this code implements a circular buffer of size 1
Upvotes: 2
Reputation: 78825
The crucial thing is executeBlockAsync
, which in turn calls dispatch_async
. So the real work is dispatched to a background queue, which is most likely configured to be working sequentially. doSomethingWithImage
immediately returns and will never take 1/30s.
So the dispatching guarantees that image processing is done serially. This works as long as image processing doesn't take more than 1/30s on average. Single images can take longer. If it persists to take longer, the queue will fill up.
I don't understand what @synchronized
does in this code. It only makes sense if there is more code (not shown here) that uses the same @synchronized
block.
Upvotes: 0