Reputation: 55583
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?
Upvotes: 1
Views: 1362
Reputation: 228
I've implemented a drop-in replacement for the NSCondition class that implements a recursive mutex: https://github.com/bradley219/NSRecursiveCondition
Upvotes: 0
Reputation: 299555
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.
Upvotes: 1