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

我只是花了一些时间在晚上玩GCD,特别是dispatch_semaphore_t因为我从来没有使用它。 从来没有需要。

所以我写了下面这个testing:

 - (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

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

只有semaphore完成后循环才会交替opacity


1.)

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

但是

看起来像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 ,包括用户界面。 没有。


4)

因此,除了我在上面指出的,我能做些什么,为了使semaphore不阻塞我的用户界面和我的其他进程,让我的用户界面和其他进程做他们的事情?

如上所述,为什么将semaphore队列的types从全局变为主要会导致屏幕上不显示任何内容,除了dispatch_group之外,甚至不会执行循环。


5.)

如果我将semaphore更改为:

 dispatch_semaphore_t mySemaphore = dispatch_semaphore_create(1); //1 instead of 0 (zero) 

一切(即循环和用户界面)运行立即和NSLog(@"Loop is Done"); 也立即显示,这告诉我信号量不在这里等待:

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

???


我花了整个晚上试图找出这个,但无济于事。 我希望有很好的GCD知识的人可以在这方面给我启发。

首先要做的是:一般来说,不要阻塞主队列。 关于不阻塞主队列的规则适用于dispatch_semaphore_wait()sleep() (以及任何同步调度,任何组等待等)。 您不应该在主队列上执行任何潜在的阻塞调用。 如果你遵循这个规则,你的用户界面不应该变成不响应。

您的代码示例和后续问题可能似乎暗示了组和信号量之间的混淆。 调度组是跟踪一组调度块的一种方式。 但是你没有在这里利用调度组的特点,所以我可能会build议把它们从讨论中删除,因为它与有关信号量的讨论无关。

另一方面,调度信号量只是一个线程向另一个等待信号的线程发送信号的机制。 不用说,事实上你已经创build了一个信号量并通过这个信号发送了信号,不会影响你的任何调度任务(不pipe是否分组),除非有问题的代码碰巧调用dispatch_semaphore_wait

最后,在后面的一些例子中,你尝试了信号量发送多个信号,或者改变创build信号量时提供的初始计数。 对于每个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个任务(例如,你正在使用networking连接(这是有限的),或者每个操作占用你想避免在任何给定时间运行超过五次的内存)。 那么你可以做一些事情:

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

再一次,这不是信号量的一个很好的例子(在这种情况下,我通常使用带有NSOperationQueuemaxConcurrentOperationCount ),但是它举例说明了为什么你要为dispatch_source_create使用一个非零值。


你已经提出了一些关于团体的问题。 我认为组织与你自己的信号量无关。 例如,如果要在所有任务完成时运行一个代码块,则可以使用一个组。 所以这里是上面例子的一个变种,但是当该组中的所有其他任务都完成时,使用dispatch_group_notify来做一些事情。

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