How to use dispatch_queue_set_specific () and dispatch_get_specific ()

I find it hard to find good examples of how to use these features.

static void * kQueue1Key = "key1"; static void * kQueue2Key = "key2"; dispatch_queue_t queue1 = dispatch_queue_create("com.company.queue1", DISPATCH_QUEUE_SERIAL); dispatch_queue_t queue2 = dispatch_queue_create("com.company.queue2", DISPATCH_QUEUE_SERIAL); dispatch_queue_set_specific(queue1, kQueue1Key, (void *)kQueue1Key, NULL); dispatch_queue_set_specific(queue2, kQueue2Key, (void *)kQueue2Key, NULL); dispatch_sync(queue1, ^{ if(dispatch_get_specific(kQueue1Key)) { NSLog(@"I'm expecting this line to run (A)"); dispatch_sync(queue2, ^{ NSLog(@"I'm expecting this line to run (B)"); if(dispatch_get_specific(kQueue2Key)) { if(dispatch_get_specific(kQueue1Key)) { NSLog(@"I'm expecting this line to run (C)"); } else { [NSException raise:NSInternalInconsistencyException format:@"Should not end up here (C)"]; } } else { [NSException raise:NSInternalInconsistencyException format:@"Should not end up here (B)"]; } }); } else { [NSException raise:NSInternalInconsistencyException format:@"Should not end up here (A)"]; } }); 

Result

 I'm expecting this line to run (A) I'm expecting this line to run (B) *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Should not end up here (C)' 

Is behavior expected? If I sent sync to queue1, since I'm not in the queue, I would be stuck. What am I missing?

+7
objective-c-blocks grand-central-dispatch
Nov 07 '13 at
source share
2 answers

Oh, here it came to my mind why you get what you get. Notes in line:

 dispatch_sync(queue1, ^{ 

When you get to this point, the "current queue" queue1

  if(dispatch_get_specific(kQueue1Key)) 

You request the current queue for the value that it has for kQueue1Key , you set it earlier so that it returns to you.

  { NSLog(@"I'm expecting this line to run (A)"); dispatch_sync(queue2, ^{ 

When you get to this point, now the "current queue" queue2

  NSLog(@"I'm expecting this line to run (B)"); if(dispatch_get_specific(kQueue2Key)) 

You request the current queue for the value that it has for kQueue2Key , you set it earlier so that it returns you.

  { if(dispatch_get_specific(kQueue1Key)) 

Now you are requesting the current queue for the value that it has for kQueue1Key . Since the current queue is queue2 and you never set the value from kQueue1Key to queue2 , you return NULL .

  { NSLog(@"I'm expecting this line to run (C)"); } else { [NSException raise:NSInternalInconsistencyException format:@"Should not end up here (C)"]; } 

The misunderstanding here is that dispatch_get_specific does not cross the stack of nested queues, it crosses the line of targeting the queue. For example, if you did this,

 static void * kQueue1Key = (void*)"key1"; static void * kQueue2Key = (void*)"key2"; dispatch_queue_t queue1 = dispatch_queue_create("com.company.queue1", DISPATCH_QUEUE_SERIAL); dispatch_queue_t queue2 = dispatch_queue_create("com.company.queue2", DISPATCH_QUEUE_SERIAL); dispatch_queue_set_specific(queue1, kQueue1Key, (void *)kQueue1Key, NULL); dispatch_queue_set_specific(queue2, kQueue2Key, (void *)kQueue2Key, NULL); // Set Queue2 to target Queue1 dispatch_set_target_queue(queue2, queue1); dispatch_sync(queue2, ^{ if(dispatch_get_specific(kQueue1Key)) { NSLog(@"I'm expecting this line to run (A)"); } else { [NSException raise:NSInternalInconsistencyException format:@"Should not end up here (C)"]; } if(dispatch_get_specific(kQueue2Key)) { NSLog(@"I'm expecting this line to run (B)"); } else { [NSException raise:NSInternalInconsistencyException format:@"Should not end up here (C)"]; } }); 

... a targeting relationship is one that passes, not a stack relationship. It would be nice if there was something that crossed the stack relationship, but I don't know anything (which you would not need to implement yourself).

+12
Nov 07 '13 at 17:42
source share
— -

As mentioned in my comment, recursive locking using dispatch_sync is generally not possible due to the possibility of targeting without it. For what it's worth, considering / assuming default targeting in the queue, here is one possible way:

 #import <unordered_set> #import <pthread.h> static dispatch_once_t recursiveLockWithDispatchQueueTLSKeyOnceToken; static pthread_key_t recursiveLockWithDispatchQueueTLSKey; typedef std::unordered_multiset<const void*> RecursiveLockQueueBag; static void freeRecursiveLockWithDispatchQueueTLSValue(void* tlsValue) { RecursiveLockQueueBag* ms = reinterpret_cast<RecursiveLockQueueBag*>(tlsValue); if (ms) delete ms; } static inline BOOL queueStackCheck(dispatch_queue_t q, BOOL checkAndPushNotPop) // If yes, check and push if not on. If no, pop. { dispatch_once(&recursiveLockWithDispatchQueueTLSKeyOnceToken, ^{ pthread_key_create(&recursiveLockWithDispatchQueueTLSKey, freeRecursiveLockWithDispatchQueueTLSValue); }); RecursiveLockQueueBag* ms = reinterpret_cast<RecursiveLockQueueBag*>(pthread_getspecific(recursiveLockWithDispatchQueueTLSKey)); if (!ms) { ms = new RecursiveLockQueueBag(); pthread_setspecific(recursiveLockWithDispatchQueueTLSKey, reinterpret_cast<const void*>(ms)); } const void* const vpq = reinterpret_cast<const void*>((__bridge const void*)q); BOOL alreadyOn = NO; if (checkAndPushNotPop) { alreadyOn = (ms->count(vpq) > 0); if (!alreadyOn) { ms->insert(vpq); } } else { ms->erase(vpq); } return alreadyOn; } void dispatch_recursive_sync(dispatch_queue_t queue, dispatch_block_t block) { if (queueStackCheck(queue, YES)) { block(); } else { @try { dispatch_sync(queue, block); } @finally { queueStackCheck(queue, NO); } } } @implementation MyAppDelegate - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { dispatch_queue_t a = dispatch_queue_create("a", DISPATCH_QUEUE_SERIAL); dispatch_queue_t b = dispatch_queue_create("b", DISPATCH_QUEUE_SERIAL); dispatch_queue_t c = dispatch_queue_create("c", DISPATCH_QUEUE_SERIAL); //dispatch_set_target_queue(a, c); dispatch_recursive_sync(a, ^{ dispatch_recursive_sync(b, ^{ dispatch_recursive_sync(c, ^{ dispatch_recursive_sync(a, ^{ dispatch_recursive_sync(b, ^{ dispatch_recursive_sync(c, ^{ dispatch_recursive_sync(a, ^{ NSLog(@"got there"); }); }); }); }); }); }); }); } @end 

This is the lowest implementation I could think of in a few minutes. I used C ++ to avoid the overhead of sending messages. This requires all use of the queue to use this function. This can be useful when there is a private queue protecting the internal state of the object (i.e., where the queue is private and therefore guaranteed that it cannot be redirected), and where you can easily make sure that all consumers of the queue use dispatch_recursive_sync .

+2
Nov 07 '13 at 14:18
source share



All Articles