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

前端 未结 1 578
伪装坚强ぢ
伪装坚强ぢ 2020-12-15 01:28

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 w

相关标签:
1条回答
  • 2020-12-15 02:08

    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");
        });
    });
    
    0 讨论(0)
提交回复
热议问题