7

我很难找到如何使用这些功能的好例子。

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)"];
    }
});

结果

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)'

这是预期的行为吗?如果我将 dispatch_sync 到 queue1,因为我不在队列中,我会死锁。我错过了什么?

4

2 回答 2

13

哦,在这里,它突然出现在我的脑海中,为什么你得到你所得到的。行内注释:

dispatch_sync(queue1, ^{

当你到达这一点时,“当前队列”是queue1

    if(dispatch_get_specific(kQueue1Key))

您正在向当前队列询问它的值kQueue1Key,您之前设置了它,因此它会将其返回给您。

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

        dispatch_sync(queue2, ^{

当你到达这一点时,“当前队列”现在是queue2

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

            if(dispatch_get_specific(kQueue2Key))

您正在向当前队列询问它的值kQueue2Key,您之前设置了它,因此它会将其返回给您。

            {

                if(dispatch_get_specific(kQueue1Key))

您现在正在向当前队列询问它对kQueue1Key. 由于当前队列是queue2并且您永远不会设置值kQueue1Keyon queue2you get back NULL

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

这里的误解是,dispatch_get_specific它不遍历嵌套队列的堆栈,而是遍历队列目标 lineage。例如,如果您改为这样做,

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)"];
    }
});

...目标关系是被遍历的关系,而不是堆栈关系。如果有一些东西可以遍历堆栈关系,那就太好了,但我什么都不知道(你不必自己实现)。

于 2013-11-07T17:42:31.640 回答
3

正如我在评论中提到的dispatch_sync,在一般情况下,由于非默认队列目标的可能性,递归锁定使用是不可能的。对于它的价值,给定/假设默认队列目标,这是一种可能的方法:

#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

这是我在几分钟内能想到的最低开销的实现。我使用 C++ 来避免消息发送开销。它要求队列的所有使用都使用此功能。当有一个私有队列保护对象的内部状态时(即队列是私有的,因此保证不会被重定向,并且您可以轻松确保队列的所有消费者使用dispatch_recursive_sync.

于 2013-11-07T14:18:57.893 回答