Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In Objective-C how do you mark a critical region?

I have a simple scenario where a NSTimer is scheduled on a background thread to send a packet through a socket every so often. My main thread however, is responsible for destroying the timer and the socket.

The destruction looks like this:

if (self.connected) {
    [self.pingTimer invalidate];

    if (self.socket != -1) {
        close(self.socket);
        self.socket = -1;
    }

    self.connected = NO;
}

How can I ensure in Objective-C that once this section of code is entered, the scheduler isn't allowed to suspend this thread until the code section is over? In other words I want to make this code section atomic.

My understanding is that the @synchronized directive is a high-level semaphore/mutex so to ensure atomicity of that code block, I would need to enclose the code block itself, as well as all the code which references either self.connected, self.pingTimer and self.socket in @synchronized directives. Am I correct in thinking this?

like image 928
lmirosevic Avatar asked Sep 21 '12 08:09

lmirosevic


2 Answers

@synchronized is one way. Another would be to use NSLock or NSRecursiveLock. You can even use plain POSIX pthread locking primitives. See the Multithreading Guide.

@interface Stuff 
{
    NSLock* lock;
    int cell;
}

- (int) cellValue;
- (void) someOperation;

@end

@implementation Stuff

- (id) init  
{
    if (self = [super init]) 
    {
        lock = [[NSLock alloc] init];
        cell = 0;
    }
    return self;
}

- (int) cellValue 
{
    [lock lock];
    @try
    {
        return cell;
    }
    @finally 
    {
        [lock unlock];
    }
}

- (void) someOperation 
{
    [lock lock];
    @try
    {
        // Do something involving access to stuff protected by
        // the lock (only cell here, but could be more, of course)
    }
    @finally 
    {
        [lock unlock];
    }
}

@end

Using @synchronized, the code above would look simpler:

- (int) cellValue 
{
    @synchronized (self)
    {
        return cell;
    }
}
like image 172
Dirk Avatar answered Oct 23 '22 03:10

Dirk


As far as I know there is no way to prevent the OS from preempting your thread and have another run in its place. The best you can do is synchronize the access. But in reading your code I infer the following:

  • connected is an internal state variable.
  • You want to avoid the timer being scheduled while your are disconnecting.
  • You want to avoid disconnecting while the timer is scheduled.

I think its best to avoid synchronization whenever possible. Fortunately, you are using a NSTimer, which will make things trivial.

NSTimer does NOT run on a background thread but on a NSRunLoop and probably on the main run loop just like your dealloc. Timers are event sources for the run loop. When they fire they enqueue an event, which in turn calls your code when it gets processed. Because run loops process one event at a time it means that the code in your scheduled timer and the code in your dealloc get fully executed before the other can run. It also means that you don't need any special synchronization mechanism.

- (void) dealloc
{
   // ...
   if (_connected) {
       [_pingTimer invalidate];
       if (_socket != -1)
           close(_socket);
   }
   // ...
   [super dealloc];
}

Note: I prefer to use the state variables directly when I can unless the setters have other side effects. There is also no point in setting state variables in dealloc simply because the object will disapear once the method call is done.

like image 30
aLevelOfIndirection Avatar answered Oct 23 '22 03:10

aLevelOfIndirection