Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Intangible Order of Execution (dispatch_semaphore_t, dispatch_group_async) and the Use of Them in Combination with Different Dispatch Queue Types

I just took some time in the evening to play around with GCD, especially with dispatch_semaphore_t because I never used it. Never had the need to.

So I wrote the following as a test:

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

If I ran the above, the output would be:

0

1

2

dispatch group 1

dispatch notify 1

3

4

5

6

7

8

9

Loop is Done

After the above, firstView appears on the screen (before semaphore the whole screen was black) and finally this gets executed:

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

Which only alternates the opacity as the loop runs after semaphore is done.


1.)

So, it seems that I have to wait until dispatch_semaphore finish to do its work before any UI thing takes place.

BUT:

It seems like dispatch_group_t runs concurrently with dispatch_semaphore as shown from the output above (i.e., 1, 2, 3, ....).

???


2.)

And if I change the for loop in the above to using: dispatch_async(dispatch_get_main_queue(), ^

instead of:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^,

nothing gets shown on the screen even after semaphore is done.

How so???


3.)

Furthermore if I change semaphore to the following as opposed to using a global queue like in the above:

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

Only dispatch_group takes place; nothing else take places / get executed, not the for loop in the above, including UI. Nothing.


4.)

So besides what I pointed out in the above, what can I do, in order to make semaphore not blocking my UI and my other process and just let my UI and the other processes do their thing?

And as mentioned above, why changing the type of queue for semaphore from global to main would cause nothing to be shown on screen and even the loop would not execute except dispatch_group?


5.)

If I change semaphore to:

dispatch_semaphore_t mySemaphore = dispatch_semaphore_create(1); 

//1 instead of 0 (zero)

Everything (i.e., both for loop and UI) runs immediately and NSLog(@"Loop is Done"); is displayed also immediately, which tells me that semaphore didn't wait here:

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

???


I spent the whole evening trying to figure this out, but to no avail. I hope someone with great GCD knowledge can enlighten me on this.

like image 779
Unheilig Avatar asked Jan 27 '14 01:01

Unheilig


1 Answers

First things first: As a general rule, one should never block the main queue. This rule about not blocking the main queue applies to both dispatch_semaphore_wait() and sleep() (as well as any of the synchronous dispatches, any group wait, etc.). You should never do any of these potentially blocking calls on the main queue. And if you follow this rule, your UI should never become non-responsive.

Your code sample and subsequent questions might seem to suggest a confusion between groups and semaphores. Dispatch groups are a way of keeping track of a group of dispatched blocks. But you're not taking advantage of the features of dispatch groups here, so I might suggest excising them from the discussion as it's irrelevant to the discussion about semaphores.

Dispatch semaphores are, on the other hand, simply a mechanism for one thread to send a signal to another thread that is waiting for the signal. Needless to say, the fact that you've created a semaphore and sent signals via that semaphore will not affect any of your dispatched tasks (whether to group or not) unless the code in question happens to call dispatch_semaphore_wait.

Finally, in some of your later examples you tried have the semaphore send multiple signals or changing the initial count to supplied when creating the semaphore. For each signal, you generally want a corresponding wait. If you have ten signals, you want ten waits.


So, let's illustrate semaphores in a way where your main queue (and thus the UI) will never be blocked. Here, we can send ten signals between two separate concurrently running tasks, having the latter one update the 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");
});

This is, admittedly, not a terribly useful example of semaphores, but it illustrates how theoretically you could use them. In practice, it's rare that you have to use semaphores, as for most business problems, there are other, more elegant coding patterns. If you describe what you're trying to do, we can show you how to best achieve it.


As for an example with non-zero value passed to dispatch_semaphore_create, that's used to control access to some finite resource. In this example, let's assume that you had 100 tasks to run, but you didn't want more than 5 to run at any given time (e.g. you're using network connections (which are limited), or the each operation takes up so much memory that you want to avoid having more than five running at any given time). Then you could do something like:

// 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);
        });
    }
});

Again, this isn't a great example of semaphores (in this case, I'd generally use an NSOperationQueue with a maxConcurrentOperationCount), but it illustrates an example of why you'd use a non-zero value for dispatch_source_create.


You've asked a number of questions about groups. I contend that groups are unrelated to your own semaphores. You might use a group, for example, if you want to run a block of code when all of the tasks are complete. So here is a variation of the above example, but using a dispatch_group_notify to do something when all of the other tasks in that group are complete.

dispatch_queue_t     queue     = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); // or create your own concurrent queue
dispatch_semaphore_t semaphore = dispatch_semaphore_create(5);
dispatch_group_t     group     = dispatch_group_create();

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

dispatch_async(queue, ^{
    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_group_async(group, queue, ^{
            NSLog(@"starting %d", i);
            // to simulate something slow happening in the background, we'll just sleep
            sleep(5);
            NSLog(@"Finishing %d", i);
            dispatch_semaphore_signal(semaphore);
        });
    }

    dispatch_group_notify(group, queue, ^{
        NSLog(@"All done");
    });
});
like image 126
Rob Avatar answered Nov 27 '22 19:11

Rob