Grand Central Dispatch中的线程限制解决方法?

使用Grand Central Dispatch ,可以轻松地在非主线程上执行耗时的任务,避免阻塞主线程并保持UI的响应。 只需使用dispatch_async并在全局并发队列上执行任务即可。

 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // code }); 

然而,这样的事情听起来太好了,而且这样做通常有其不利之处。 在我们的iOS应用项目中,我们使用了很多,最近我们发现它有64个线程的限制。 一旦我们达到极限,应用程序将冻结/挂起。 通过暂停与Xcode的应用程序,我们可以看到主线程是由semaphore_wait_trap举行。

谷歌在网上search确认其他人也遇到这个问题,但迄今没有find解决办法。

调度线程硬限制达到:64(在同步操作中阻塞的调度线程太多)

另一个stackoverflow问题确认使用dispatch_syncdispatch_barrier_async也会发生此问题。

题:
由于Grand Central Dispatch有64个线程的限制,有没有解决方法?

提前致谢!

那么,如果你有约束力,你可以把GCD的束缚解放出来,然后用pthreads直接对付每个进程的线程限制,但是底线是这样的:如果你碰到GCD中的队列宽度限制,您可能需要考虑重新评估您的并发方法。

在极端情况下,有两种方法可以达到极限:

  1. 你可以通过一个阻塞的系统调用在一些OS原语上阻塞64个线程。 (I / O绑定)
  2. 您可以合理地拥有64个可运行的任务,可以同时处理这些任务。 (CPU绑定)

如果您处于#1情况,那么推荐的方法是使用非阻塞I / O。 事实上,GCD在10.7 / Lion IIRC中引入了大量的调用,这些调用促进了I / O的asynchronous调度并改善了线程的重用。 如果你使用GCD I / O机制,那么这些线程将不会被束缚在等待I / O上,当文件描述符(或者mach端口)上的数据变得可用时,GCD将只是排队你的数据块(或函数)。 请参阅dispatch_io_create和朋友的文档。

如果有帮助,下面是使用GCD I / O机制实现的TCP回显服务器的一个小例子(不提供保修):

 in_port_t port = 10000; void DieWithError(char *errorMessage); // Returns a block you can call later to shut down the server -- caller owns block. dispatch_block_t CreateCleanupBlockForLaunchedServer() { // Create the socket int servSock = -1; if ((servSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { DieWithError("socket() failed"); } // Bind the socket - if the port we want is in use, increment until we find one that isn't struct sockaddr_in echoServAddr; memset(&echoServAddr, 0, sizeof(echoServAddr)); echoServAddr.sin_family = AF_INET; echoServAddr.sin_addr.s_addr = htonl(INADDR_ANY); do { printf("server attempting to bind to port %d\n", (int)port); echoServAddr.sin_port = htons(port); } while (bind(servSock, (struct sockaddr *) &echoServAddr, sizeof(echoServAddr)) < 0 && ++port); // Make the socket non-blocking if (fcntl(servSock, F_SETFL, O_NONBLOCK) < 0) { shutdown(servSock, SHUT_RDWR); close(servSock); DieWithError("fcntl() failed"); } // Set up the dispatch source that will alert us to new incoming connections dispatch_queue_t q = dispatch_queue_create("server_queue", DISPATCH_QUEUE_CONCURRENT); dispatch_source_t acceptSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, servSock, 0, q); dispatch_source_set_event_handler(acceptSource, ^{ const unsigned long numPendingConnections = dispatch_source_get_data(acceptSource); for (unsigned long i = 0; i < numPendingConnections; i++) { int clntSock = -1; struct sockaddr_in echoClntAddr; unsigned int clntLen = sizeof(echoClntAddr); // Wait for a client to connect if ((clntSock = accept(servSock, (struct sockaddr *) &echoClntAddr, &clntLen)) >= 0) { printf("server sock: %d accepted\n", clntSock); dispatch_io_t channel = dispatch_io_create(DISPATCH_IO_STREAM, clntSock, q, ^(int error) { if (error) { fprintf(stderr, "Error: %s", strerror(error)); } printf("server sock: %d closing\n", clntSock); close(clntSock); }); // Configure the channel... dispatch_io_set_low_water(channel, 1); dispatch_io_set_high_water(channel, SIZE_MAX); // Setup read handler dispatch_io_read(channel, 0, SIZE_MAX, q, ^(bool done, dispatch_data_t data, int error) { BOOL close = NO; if (error) { fprintf(stderr, "Error: %s", strerror(error)); close = YES; } const size_t rxd = data ? dispatch_data_get_size(data) : 0; if (rxd) { // echo... printf("server sock: %d received: %ld bytes\n", clntSock, (long)rxd); // write it back out; echo! dispatch_io_write(channel, 0, data, q, ^(bool done, dispatch_data_t data, int error) {}); } else { close = YES; } if (close) { dispatch_io_close(channel, DISPATCH_IO_STOP); dispatch_release(channel); } }); } else { printf("accept() failed;\n"); } } }); // Resume the source so we're ready to accept once we listen() dispatch_resume(acceptSource); // Listen() on the socket if (listen(servSock, SOMAXCONN) < 0) { shutdown(servSock, SHUT_RDWR); close(servSock); DieWithError("listen() failed"); } // Make cleanup block for the server queue dispatch_block_t cleanupBlock = ^{ dispatch_async(q, ^{ shutdown(servSock, SHUT_RDWR); close(servSock); dispatch_release(acceptSource); dispatch_release(q); }); }; return Block_copy(cleanupBlock); } 

无论如何…回到手头的话题:

如果你处于第二种情况,你应该问自己:“我真的通过这种方法获得了什么? 假设您拥有MacPro最为出色的12个内核,24个超线程/虚拟内核。 有64个线程,你有一个约。 3:1线程虚拟核心比例。 上下文切换和caching未命中不是免费的。 请记住,我们假设你不是I / O绑定的这种情况,所以你所做的是通过拥有比内核更多的任务来浪费CPU时间,通过上下文切换和caching溢出。

实际上,如果您的应用程序因为已达到队列宽度限制而挂起,那么最可能的情况是您的队列已经匮乏。 您可能创build了一个减less到死锁的依赖项。 我经常见到的情况是当多个互锁线程试图在同一个队列上dispatch_sync时,当没有剩下线程时。 这总是失败。

原因如下:队列宽度是一个实现细节。 GCD的64线程宽度限制是没有logging的,因为devise良好的并发体系结构不应该依赖于队列宽度。 你应该总是devise你的并发体系结构,这样一个2线程宽的队列最终将作为一个1000线程宽的队列完成相同的结果(如果慢)。 如果你不这样做,总会有一个机会,你的队伍将饿死。 把你的工作量分成可并行的单位应该让自己开始优化的可能性,而不是基本function的要求。 在开发过程中强制执行此规则的一种方法是尝试在使用并发队列的地方使用串行队列,但期望不会发生互锁行为。 执行像这样的检查将帮助您更早地捕获这些错误(但不是全部)。

另外,对于你原来的问题:IIUC,64个线程的限制是每个顶级并发队列有64个线程,所以如果你真的觉得需要,你可以使用全部三个顶级并发队列(Default,High和Low优先级)达到64个以上的线程总数。 请不要这样做。 修复你的devise,使其不会自己挨饿。 你会更快乐。 无论如何,正如我上面所暗示的那样,如果你正在耗尽一个64线程的队列,那么你最终可能只是填满全部三个顶级队列和/或运行到每个进程的线程限制,并且也是如此。