ios GCD创建的线程数?

yiytaume  于 2023-07-01  发布在  iOS
关注(0)|答案(4)|浏览(120)

有没有关于GCD创建了多少线程的好文档?在WWDC上,他们告诉我们它是围绕CPU核心建模的。但是,如果我调用这个例子:

for (int i=1; i<30000; i++) {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [NSThread sleepForTimeInterval:100000];
    });
}

它会打开66个线程,即使是在iPad1上。(在Lion上调用时,它也会打开66个线程)。为什么是66?

qnzebej0

qnzebej01#

首先,66 == 64(最大GCD线程池大小)+主线程+其他一些随机的非GCD线程。
其次,GCD不是魔法。它被优化为使CPU忙碌处理大部分CPU受限的代码。GCD的“魔力”在于,当工作项无意中短暂地等待操作完成时,它会动态地创建比CPU更多的线程。
话虽如此,代码可能会通过故意休眠或等待事件而不是使用分派源来等待事件来混淆GCD调度程序。在这些场景中,工作块有效地实现了自己的调度器,因此GCD必须假设线程是从线程池中增选的。
简而言之,如果您的代码更喜欢dispatch_after()而不是像API那样的“sleep()”,并且更喜欢通过手工事件循环(Unix select()/poll(),可可runloops或POSIX条件变量)来调度源,则线程池将以最佳方式运行。

zhte4eai

zhte4eai2#

文档避免提及创建的线程数。这主要是因为线程的最佳数量在很大程度上取决于上下文。
Grand Cendral Dispatch的一个问题是,如果正在运行的任务阻塞,它将产生一个新线程。也就是说,在使用GCD时应该避免阻塞,因为线程数多于内核数是次优的。
在您的例子中,GCD检测到任务处于非活动状态,并为下一个任务生成一个新线程。
为什么66岁是我的极限。

2ekbmq32

2ekbmq323#

答案应该是:512
有不同的情况:

  • 所有类型(并发和串行队列)最多可以同时创建512个线程。
  • 所有全局队列最多可以同时创建64个线程。

| 所有类型(并发和串行队列)|所有全局队列| All global queues |
| --|--| ------------ |
| 512螺纹|64螺纹| 64 threads |
一般来说
如果应用程序中的线程数超过64,则会导致主线程滞后。在严重的情况下,它甚至可能触发看门狗崩溃。这是因为创建线程会产生开销,iOS下的主要成本是:内核数据结构(约1 KB),堆栈空间(子线程512 KB,主线程1 MB)也可以使用-setStackSize设置,但必须是4K的倍数,最小为16 K,创建一个线程大约需要90 ms。打开大量线程会降低程序的性能。线程越多,调度它们的CPU开销就越大。程序设计比较复杂:线程间的通信和多线程间的数据共享。
首先,GCD对它可以创建的线程数量有限制,
gcd创建线程的方式是调用_pthread_workqueue_addthreads,因此创建的线程数量是有限制的。创建线程的其他方法不调用此创建方法。
回到上面的问题:
所有全局队列最多可以同时创建64个线程。
全局队列通过_pthread_workqueue_addthreads方法添加。但是,在内核(插件)中使用此方法添加的线程数量有限制。
限制的具体代码如下:

#define MAX_PTHREAD_SIZE 64*1024

此代码的含义如下:
根据Apple-Docs-Threading Programming Guide- Thread creation costs,总大小限制为64 k:1个线程分配1 k个核心,因此我们可以推断结果是64个线程。

总而言之,全局队列最多可以创建64个线程,这在内核(插件)中是写死的。
更详细的测试代码如下:

  • 测试环境:iOS 14.3
  • 测试代码

情况1:全局队列- CPU忙碌
在第一个测试用例中,我们使用dispatch_get_global_queue(0, 0)来获取默认的全局队列并模拟CPU忙碌。

+ (void)printThreadCount {
       kern_return_t kr = { 0 };
       thread_array_t thread_list = { 0 };
       mach_msg_type_number_t thread_count = { 0 };
       kr = task_threads(mach_task_self(), &thread_list, &thread_count);
       if (kr != KERN_SUCCESS) {
           return;
       }
       NSLog(@"threads count:%@", @(thread_count));

       kr = vm_deallocate( mach_task_self(), (vm_offset_t)thread_list, thread_count * sizeof(thread_t) );
       if (kr != KERN_SUCCESS) {
           return;
       }
       return;
   }

   + (void)test1 {
       NSMutableSet<NSThread *> *set = [NSMutableSet set];
       for (int i=0; i < 1000; i++) {
           dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
           dispatch_async(queue, ^{
               NSThread *thread = [NSThread currentThread];
               [set addObject:[NSThread currentThread]];
               dispatch_async(dispatch_get_main_queue(), ^{
                   NSLog(@"start:%@", thread);
                   NSLog(@"GCD threads count:%lu",(unsigned long)set.count);
                   [self printThreadCount];
               });

               NSDate *date = [NSDate dateWithTimeIntervalSinceNow:10];
               long i=0;
               while ([date compare:[NSDate date]]) {
                   i++;
               }
               [set removeObject:thread];
               NSLog(@"end:%@", thread);
           });
       }
   }

测试:线程数为2
情况2:全局队列- CPU空闲
对于第二个代码,我们使用[NSThread sleepForTimeInterval:10]进行测试;模拟CPU空闲

+ (void)test2 {
       NSMutableSet<NSThread *> *set = [NSMutableSet set];
       for (int i=0; i < 1000; i++) {
           dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
           dispatch_async(queue, ^{
               NSThread *thread = [NSThread currentThread];
               [set addObject:[NSThread currentThread]];
               dispatch_async(dispatch_get_main_queue(), ^{
                   NSLog(@"start:%@", thread);
                   NSLog(@"GCD threads count:%lu",(unsigned long)set.count);
                   [self printThreadCount];
               });
               // thread sleep for 10s
               [NSThread sleepForTimeInterval:10];
               [set removeObject:thread];
               NSLog(@"end:%@", thread);
               return;
           });
       }
   }

经过测试,最大线程数为64

所有并发队列和串行队列最多可同时创建512个线程。

更详细的测试代码如下:
case 1:自建队列- CPU忙碌
现在,让我们来看看自建队列的性能-- CPU忙碌。此示例将模拟大多数APP场景,其中不同的业务方创建单独的队列来管理其任务。

+ (void)test3 {
       NSMutableSet<NSThread *> *set = [NSMutableSet set];
       for (int i=0; i < 1000; i++) {
           const char *label = [NSString stringWithFormat:@"label-:%d", i].UTF8String;
           NSLog(@"create:%s", label);
           dispatch_queue_t queue = dispatch_queue_create(label, DISPATCH_QUEUE_SERIAL);
           dispatch_async(queue, ^{
               NSThread *thread = [NSThread currentThread];
               [set addObject:[NSThread currentThread]];

               dispatch_async(dispatch_get_main_queue(), ^{
                   static NSInteger lastCount = 0;
                   if (set.count <= lastCount) {
                       return;
                   }
                   lastCount = set.count;
                   NSLog(@"begin:%@", thread);
                   NSLog(@"GCD threads count量:%lu",(unsigned long)set.count);
                   [self printThreadCount];
               });

               NSDate *date = [NSDate dateWithTimeIntervalSinceNow:10];
               long i=0;
               while ([date compare:[NSDate date]]) {
                   i++;
               }
               [set removeObject:thread];
               NSLog(@"end:%@", thread);
           });
       }
   }

经测试,GCD创建的线程数最大为512
case 2:自建队列- CPU空闲

+ (void)test4 {
       NSMutableSet<NSThread *> *set = [NSMutableSet set];
       for (int i=0; i < 10000; i++) {
           const char *label = [NSString stringWithFormat:@"label-:%d", i].UTF8String;
           NSLog(@"create:%s", label);
           dispatch_queue_t queue = dispatch_queue_create(label, DISPATCH_QUEUE_SERIAL);
           dispatch_async(queue, ^{
               NSThread *thread = [NSThread currentThread];

               dispatch_async(dispatch_get_main_queue(), ^{
                   [set addObject:thread];
                   static NSInteger lastCount = 0;
                   if (set.count <= lastCount) {
                       return;
                   }
                   lastCount = set.count;
                   NSLog(@"begin:%@", thread);
                   NSLog(@"GCD threads count:%lu",(unsigned long)set.count);
                   [self printThreadCount];
               });

               [NSThread sleepForTimeInterval:10];
               dispatch_async(dispatch_get_main_queue(), ^{
                   [set removeObject:thread];
                   NSLog(@"end:%@", thread);
               });
           });
       }
   }

自建队列- CPU空闲最大创建线程数为512

其他测试代码

__block int index = 0;
   
// one concurrent  queue test
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i < 1000; ++i) {
   dispatch_async(queue, ^{
       id name = nil;
       @synchronized (self) {
           name = [NSString stringWithFormat:@"gcd-limit-test-global-concurrent-%d", index];
           index += 1;
       }
       NSThread.currentThread.name = name;
       NSLog(@"%@", name);
       sleep(100000);
   });
}
// some concurrent queues test
for (int i = 0; i < 1000; ++i) {
   char buffer[256] = {};
   sprintf(buffer, "gcd-limit-test-concurrent-%d", i);
   dispatch_queue_t queue = dispatch_queue_create(buffer, DISPATCH_QUEUE_CONCURRENT);
   dispatch_async(queue, ^{
       id name = nil;
       @synchronized (self) {
           name = [NSString stringWithFormat:@"gcd-limit-test-concurrent-%d", index];
           index += 1;
       }
       NSThread.currentThread.name = name;
       NSLog(@"%@", name);
       sleep(100000);
   });
}
// some serial queues test
for (int i = 0; i < 1000; ++i) {
   char buffer[256] = {};
   sprintf(buffer, "gcd-limit-test-%d", i);
   dispatch_queue_t queue = dispatch_queue_create(buffer, 0);
   dispatch_async(queue, ^{
       id name = nil;
       @synchronized (self) {
           name = [NSString stringWithFormat:@"gcd-limit-test-%d", index];
           index += 1;
       }
       NSThread.currentThread.name = name;
       NSLog(@"%@", name);
       sleep(100000);
   });
}

特别注意:
所提到的512是gcd的极限。打开512 gcd线程后,您仍然可以使用NSThread打开它们。
所以上面的图表
当前应该是512,516 = 512(max)+主线程+ js线程+ web线程+ uikit事件线程”

总结

经过测试,GCD的全局队列会自动将线程数量限制在一个合理的范围内。与此相比,自建队列创建的线程数量很大。
考虑到线程数量太大,CPU调度成本会增加。
因此,建议小型APP尽量使用全局队列来管理任务;大型APP可以根据自身实际情况决定合适的解决方案。

6g8kf2rb

6g8kf2rb4#

忙碌线程数等于CPU核心数。被阻止的线程(您使用sleepForTimeInterval阻止它们)不被计算在内。

如果你把代码改成这样(Swift):

for _ in 1..<30000 {
    DispatchQueue.global().async {
        while true {}
     }
}

你会看到只有2个线程创建(在iPhone SE上):

有一个线程限制,以便您的应用程序不会因为大内存消耗而被杀死,如果您遇到阻塞线程的问题(通常是因为死锁)。
永远不要阻塞线程,这样你就可以像拥有核心一样拥有它们。

相关问题