1

I'm implementing a Future class in Objective-C, akin to the Java's java.util.concurrent.Future, but better suited to my simple needs. Here's the implementation:

typedef id (^TaskBlock)();

static int NotDone = 0;
static int Done = 1;

@implementation Future

- (id)initWithBlock:(TaskBlock)aBlock {
    if((self = [super init])) {
        lock = [[NSConditionLock alloc] initWithCondition:NotDone];

        block = aBlock;
    }

    return self;
}

- (void)set:(id)aValue {
    if ([lock tryLockWhenCondition:NotDone]) {
        [lock lockWhenCondition:NotDone];
        value = aValue;
        [lock unlockWithCondition:Done];
    }
}

- (id)get {
    DLog(@"Retrieving future value");

    [lock lockWhenCondition:Done];
    id v = value;
    [lock unlock];

    return v;
}

- (void)start {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        DLog(@"Requesting future value");

        [self set:block()];

        DLog(@"Future value set");
    });
}

@end

When I'm using it, one thread first gets blocked in get: like this (it waits for a lengthy computation represented by block to finish):

frame #0: 0x9139783e libsystem_kernel.dylib`__psynch_cvwait + 10
frame #1: 0x904a4e78 libsystem_c.dylib`_pthread_cond_wait + 914
frame #2: 0x904a4f7b libsystem_c.dylib`pthread_cond_timedwait_relative_np + 47
frame #3: 0x00b565f2 Foundation`-[NSCondition waitUntilDate:] + 389
frame #4: 0x00b26fc0 Foundation`-[NSConditionLock lockWhenCondition:beforeDate:] + 285
frame #5: 0x00b26e9d Foundation`-[NSConditionLock lockWhenCondition:] + 69
frame #6: 0x0000c335 Jungle`-[Future get](self=0x0757e220, _cmd=0x0000ec3a) + 85 at Future.m:52
frame #7: 0x0000ab23 Jungle`-[GADelayedBinding waitDelayed](self=0x071c39e0, _cmd=0x0000eb1d) + 99 at GADelayedBinding.m:44
frame #8: 0x00007a26 Jungle`-[GATuple waitDelayed](self=0x071baa70, _cmd=0x0000eb1d) + 390 at GATuple.m:99
frame #9: 0x000098fa Jungle`-[GAUnifier unify:with:dynamicContext:](self=0x07561030, _cmd=0x0000eaf8, query=0x071bcc50, base=0x07182f40, dynamicContext=0x00000000) + 2442 at GAUnifier.m:85
frame #10: 0x00009b06 Jungle`-[GAUnifier unify:with:](self=0x07561030, _cmd=0x0000e71e, query=0x071bcc50, base=0x07182f40) + 118 at GAUnifier.m:93
frame #11: 0x00002b44 Jungle`__32-[ViewController viewDidAppear:]_block_invoke(.block_descriptor=0x0756a6a0) + 84 at ViewController.m:38
frame #12: 0x04a0a53f libdispatch.dylib`_dispatch_call_block_and_release + 15
frame #13: 0x04a1c014 libdispatch.dylib`_dispatch_client_callout + 14
frame #14: 0x04a0d2e8 libdispatch.dylib`_dispatch_root_queue_drain + 335
frame #15: 0x04a0cfcb libdispatch.dylib`_dispatch_worker_thread3 + 20
frame #16: 0x904a2b24 libsystem_c.dylib`_pthread_wqthread + 346

When computation finishes, I'm getting an error about a deadlock here:

frame #0: 0x00b9f80f Foundation`_NSLockError
frame #1: 0x00b26f6f Foundation`-[NSConditionLock lockWhenCondition:beforeDate:] + 204
frame #2: 0x00b26e9d Foundation`-[NSConditionLock lockWhenCondition:] + 69
frame #3: 0x0000c268 Jungle`-[Future set:](self=0x0757e220, _cmd=0x0000ec35, aValue=0x07578b40) + 136 at Future.m:43
frame #4: 0x0000c501 Jungle`__15-[Future start]_block_invoke(.block_descriptor=0x0757e330) + 113 at Future.m:63
frame #5: 0x04a0a53f libdispatch.dylib`_dispatch_call_block_and_release + 15
frame #6: 0x04a1c014 libdispatch.dylib`_dispatch_client_callout + 14
frame #7: 0x04a0d2e8 libdispatch.dylib`_dispatch_root_queue_drain + 335
frame #8: 0x04a0cfcb libdispatch.dylib`_dispatch_worker_thread3 + 20
frame #9: 0x904a2b24 libsystem_c.dylib`_pthread_wqthread + 346

What do I do wrong? All conditions that my lock is awaiting on look correct to me.

Sergey Mikhanov
  • 8,350
  • 9
  • 40
  • 54

1 Answers1

2

The method tryLockWhenCondition: actually acquires the lock when it succeeds so you are attempting to lock it twice which results in a deadlock.

if ([lock tryLockWhenCondition:NotDone]) {
    [lock lockWhenCondition:NotDone]; //Remove this

I would like to also point out that what you are attempting to do is the default behavior for properties when you do not specify nonatomic.

Joe
  • 55,599
  • 8
  • 122
  • 132
  • I agree - what' the point if Cocoa/Xcode gives us `nonatomic` for free?? – Jay Mar 28 '13 at 07:07
  • Thanks, that was precisely the issue. Speaking of `nonatomic`, I don't think it gives me a possibility to perform a lengthy computation of the value in separate thread (see `start` method in the code above), while all other threads will be blocked in `get:` method, does it? – Sergey Mikhanov Mar 28 '13 at 09:34
  • It does not look like you are doing anything special. When declare an `@property` and do **not** specify `nonatomic` then access across is atomic. Now if you needed to perform an additional lock outside of the getter or setter then yes you would need to do what you are doing. – Joe Mar 28 '13 at 11:54
  • You can find some details on `atomic` properties to make your own decision here: http://stackoverflow.com/questions/588866/atomic-vs-nonatomic-properties – Joe Mar 28 '13 at 12:02