Question

I am working on creating a blocking queue, accessed by about 10 worker threads simultaneously. The basic implementation of the queue is like this:

-(void) enqueue:(__strong id)value
{
    [_mutex lock];

    while ([self size] == _maxSize) {
        [_mutex wait];
    }

    [_queue enqueue:value];
    [_mutex signal];
    [_mutex unlock];
}

-(id) dequeue
{    
    [_mutex lock];

    while ([self isEmpty]) {
        [_mutex wait];
    }

    id value = [_queue dequeue];
    [_mutex broadcast];

    [_mutex unlock];
    return value;
}

Where _mutex is a NSCondition. The problems come in with the -isEmpty and -size methods:

-(int) size
{
    @try {
        [_mutex lock];

        return [_queue size];
    }
    @finally {
        [_mutex unlock];        
    }
}

-(BOOL) isEmpty
{
    @try {
        [_mutex lock];

        return [_queue isEmpty];
    }
    @finally {    
        [_mutex unlock];
    }
}

Because they require a lock on the mutex to make sure no data corruption is in place, it puts the program into a deadlock, as NSCondition does not lock recursively. However, If I change my implementation to the following:

-(void) enqueue:(__strong id)value
{
    while ([self size] == _maxSize) {
        [_mutex lock];
        [_mutex wait];
        [_mutex unlock];
    }

    [_mutex lock];
    [_queue enqueue:value];
    [_mutex signal];
    [_mutex unlock];
}

-(id) dequeue
{
    while ([self isEmpty]) {
        [_mutex lock];
        [_mutex wait];
        [_mutex unlock];
    }

    [_mutex lock]; // when I require the lock here, another thread has already dequeued the object
    id value = [_queue dequeue];
    [_mutex broadcast];

    [_mutex unlock];
    return value;
}

Then the program does not deadlock, however, by the time I re-aquire the lock, another worker has dequeued the object I need already. Any thoughts on how to make a NSCondition recursive?

Was it helpful?

Solution

I generally use the following pattern:

-(int)primitiveSize
{
  return [_queue size];
}

Methods prefixed with primitive in ObjC (coming from the Core Data naming tradition) suggest that they have no side effects, no funny business, no conversions, just-give-me-the-value. This way, you can use primitiveSize in cases where you already have acquired a lock without forsaking encapsulation.

This is much faster than creating a recursive mutex BTW.

OTHER TIPS

I've implemented a drop-in replacement for the NSCondition class that implements a recursive mutex: https://github.com/bradley219/NSRecursiveCondition

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top