无形执行顺序(dispatch_semaphore_t,dispatch_group_async)及其与不同调度队列类型的结合使用

时间:2014-01-27 01:53:17

标签: ios grand-central-dispatch

我只是花了一些时间在晚上玩GCD,尤其是dispatch_semaphore_t因为我从未使用它。从来没有必要。

所以我写了以下内容作为测试:

- (void)viewDidLoad
{
        UIView *firstView = [[UIView alloc] initWithFrame:(CGRect){{0, 0}, self.view.frame.size.width/4, self.view.frame.size.width/5}];
        firstView.backgroundColor = [UIColor purpleColor];
        [self.view addSubview:firstView];


        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
        {
            for (long i = 0; i < 1000; i++)
            {
                sleep(5);
                dispatch_async(dispatch_get_main_queue(), ^
                {
                    firstView.layer.opacity = ((i%2) ? 0: 1);
                });
            }
        });

        dispatch_queue_t queue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        dispatch_group_t group1 = dispatch_group_create();

        dispatch_group_async(group1, queue1, ^
        {
             sleep(3);
             NSLog(@"dispatch group 1");
        });

        dispatch_group_notify(group1, queue1, ^
        {
            NSLog(@"dispatch notify 1");
        });

        dispatch_async(myQueue, ^
        {
            for(int z = 0; z < 10; z++)
            {
                NSLog(@"%i", z);
                sleep(1);
            }
            dispatch_semaphore_signal(mySemaphore);
       });

       dispatch_semaphore_wait(mySemaphore, DISPATCH_TIME_FOREVER);
       NSLog(@"Loop is Done");
}

如果我运行上述操作,输出将为:

  

0

     

1

     

2

     

派遣小组1

     

调度通知1

     

3

     

4

     

5

     

6

     

7

     

8

     

9

     

循环完成

完成上述操作后,屏幕上会显示firstView(整个屏幕显示为semaphore之前),最后会执行此操作:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
{
    for (long i = 0; i < 1000; i++)
    {
        sleep(5);
        dispatch_async(dispatch_get_main_queue(), ^
        {
            firstView.layer.opacity = ((i%2) ? 0: 1);
        });
    }
});

opacity完成后循环运行时,会替换semaphore


1。)

所以,在任何UI事件发生之前,似乎我必须等到dispatch_semaphore完成它的工作。

BUT

似乎dispatch_group_tdispatch_semaphore同时运行,如上面的输出所示(即1,2,3,....)。

???


2。)

如果我将上面的for loop更改为使用:dispatch_async(dispatch_get_main_queue(), ^

而不是:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^

即使在semaphore完成后,屏幕上也没有显示任何内容。

怎么样???


第3。)

此外,如果我将semaphore更改为以下内容而不是像上面那样使用全局队列:

dispatch_async(dispatch_get_main_queue(), ^
{
    for(int z = 0; z < 10; z++)
    {
        NSLog(@"%i", z);
        sleep(1);
    }
    dispatch_semaphore_signal(mySemaphore);
});

仅发生dispatch_group次;没有别的地方/被执行,而不是上面的for loop,包括UI。什么都没有。


4)。

除了我在上面指出的内容之外,我还能做些什么,以使semaphore不阻止我的UI和我的其他进程,让我的UI和其他进程做他们的事情?

如上所述,为什么将semaphore的队列类型从全局更改为main将导致屏幕上无法显示任何内容,甚至除了dispatch_group外,循环也不会执行?


5。)

如果我将semaphore更改为:

dispatch_semaphore_t mySemaphore = dispatch_semaphore_create(1); 

//1 instead of 0 (zero)

所有内容(即for循环和UI)都会立即运行,NSLog(@"Loop is Done");也会立即显示,这告诉我信号量不会在此等待:

dispatch_semaphore_wait(mySemaphore, DISPATCH_TIME_FOREVER);
NSLog(@"Loop is Done");

???


我花了整整一个晚上试图解决这个问题,但无济于事。 我希望有GCD知识的人能够在这方面给我启发。

1 个答案:

答案 0 :(得分:14)

首先要做的事情:作为一般规则,永远不应该阻止主队列。关于不阻塞主队列的规则适用于dispatch_semaphore_wait()sleep()(以及任何同步调度,任何组等待等)。您永远不应该在主队列上执行任何这些可能阻塞的调用。如果您遵循此规则,您的UI永远不会变得无响应。

您的代码示例和后续问题似乎表明群组和信号量之间存在混淆。调度组是一种跟踪一组调度块的方法。但是你没有在这里利用调度组的功能,所以我建议将它们从讨论中删除,因为它与关于信号量的讨论无关。

另一方面,调度信号量只是一个线程将信号发送到另一个等待信号的线程的机制。毋庸置疑,您创建信号量并通过该信号量发送信号这一事实不会影响您的任何调度任务(无论是否分组),除非相关代码恰好调用dispatch_semaphore_wait

最后,在稍后的一些示例中,您尝试让信号量发送多个信号或在创建信号量时更改提供的初始计数。对于每个signal,您通常需要相应的wait。如果您有十个信号,则需要十次等待。


因此,让我们以一种永远不会阻止主队列(以及UI)的方式来说明信号量。在这里,我们可以在两个独立的并发运行任务之间发送十个信号,后者更新UI:

dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

// send 10 signals from one background thread

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
    for (NSInteger i = 0; i < 10; i++) {
        NSLog(@"Sleeping %d", i);
        sleep(3);
        NSLog(@"Sending signal %d", i);
        dispatch_semaphore_signal(semaphore);
    }

    NSLog(@"Done signaling");
});

// and on another thread, wait for those 10 signals ...

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
    for (NSInteger i = 0; i < 10; i++) {
        NSLog(@"Waiting for signal %d", i);
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"Got signal %d", i);

        // if you want to update your UI, then dispatch that back to the main queue

        dispatch_async(dispatch_get_main_queue(), ^{
            // update your UI here
        });
    }

    NSLog(@"Done waiting");
});

诚然,这不是一个非常有用的信号量示例,但它说明了理论上你可以如何使用它们。在实践中,很少有必须使用信号量,因为对于大多数业务问题,还有其他更优雅的编码模式。如果你描述了你想要做的事情,我们可以告诉你如何最好地实现它。


对于传递给dispatch_semaphore_create的非零值的示例,该示例用于控制对某些有限资源的访问。在这个例子中,让我们假设您有100个任务要运行,但是您不希望在任何给定时间运行超过5个(例如,您正在使用网络连接(这是有限的),或者每个操作都占用了你要避免在任何给定时间运行超过五次的大量内存。然后你可以做类似的事情:

// we only want five to run at any given time

dispatch_semaphore_t semaphore = dispatch_semaphore_create(5);

// send this to background queue, so that when we wait, it doesn't block main queue

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    for (NSInteger i = 0; i < 100; i++)
    {
        // wait until one of our five "slots" are available

        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

        // when it is, dispatch code to background queue

        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSLog(@"starting %d", i);
            // to simulate something slow happening in the background, we'll just sleep
            sleep(5);
            NSLog(@"Finishing %d", i);

            // when done, signal that this "slot" is free (please note, this is done
            // inside the dispatched block of code)

            dispatch_semaphore_signal(semaphore);
        });
    }
});

同样,这不是信号量的一个很好的例子(在这种情况下,我通常使用带有NSOperationQueue的{​​{1}}),但它说明了你使用信号的一个例子maxConcurrentOperationCount的非零值。


您已经询问了很多关于群组的问题。我认为群体与你自己的信号量无关。例如,如果要在所有任务完成时运行代码块,则可以使用组。所以这是上面示例的变体,但是当该组中的所有其他任务完成时,使用dispatch_source_create执行某些操作。

dispatch_group_notify