一个进程最多创建多少个线程?

由于每个线程都有自己独立的栈空间,因此,创建的线程数量会受到栈空间大小的限制

我们使用 ulimit -s 查看栈的大小:

[root@localhost test]# ulimit -s
8192

对于 32 位 OS 来说,用户空间的虚拟内存最大为 3G,那么,理论上一个进程能创建的线程数量为 3G / 8192K = 384 个

但是,实际数量通常还会更低,一个线程所需的虚拟内存空间应该大于 8M(包括了 TLS,以及内核管理的 TCB)

做个实验:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>

void *task(void *arg)
{
    while (1)
    {
        sleep(100);
    }
}

int main(void)
{
    int cnt = 0, err = 0;
    pthread_t tid;
    while (err == 0)
    {
        err = pthread_create(&tid, NULL, task, NULL);
        ++cnt;
    }
    printf("pid: %d\ntotal thread number: %d\nerror: %s", getpid(), cnt, strerror(errno));
    getchar();
}

由于我没有 32 位的环境,从网上扒了一个别人的结果:

大约为 300 个

对于 64 位 OS,用户虚拟内存空间通常为 128T,理论上可以创建千万级别的线程

但是实际并不是如此,线程数还取决于:

  • /proc/sys/kernel/threads-max,表示系统支持的最大线程数
  • /proc/sys/kernel/pid_max,表示系统全局的 PID 号数值的限制,每一个进程或线程都有 ID,ID 的值超过这个数,进程或线程就会创建失败;
  • /proc/sys/vm/max_map_count,表示限制一个进程可以拥有的 VMA(虚拟内存区域)的数量,超过该数量,就无法为该进程分配内存了

在我的环境(64 位,2 Cores,4G RAM),运行结果如下:

[root@localhost test]# cat /proc/sys/kernel/threads-max
29918

已经达到系统全局支持的最大线程数了

总结一波:

  • 进程创建的线程数与 虚拟内存空间大小、栈空间大小 有关
  • 对于 64 位 OS,由于虚拟内存空间很大,基本上不存在虚拟内存不足导致线程无法创建
  • 对于 64 位 OS,限制线程的最大创建数的主要原因取决于:/proc/sys/kernel/threads-max/proc/sys/kernel/pid_max/proc/sys/vm/max_map_count

一个线程崩溃了,所属进程也会崩溃吗?

在 C/C++,默认是这样的

看一个代码:

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
#include <errno.h>
#include <signal.h>

void *task(void *arg)
{
    sleep(1);

    int *p = NULL;
    *p = 1; // null pointer error

    printf("sub thread exit\n");
    return NULL;
}

int main(void)
{
    pthread_t tid;
    pthread_create(&tid, NULL, task, NULL);
    pthread_join(tid, NULL);
    printf("main thread exit\n");
}

运行结果:

[root@localhost test]# clang test.c -o test -pthread
[root@localhost test]# ./test
Segmentation fault (core dumped)

可以发现,主线程还没有输出 main thread exit,进程就退出了

事实上,崩溃的本质是内核向进程发出了 SIGSEGV 信号,而 SIGSEGV 是段错误

由于线程之间(包括主线程)共享 mm_struct,内核认为如果出现段错误,如果进程不退出,可能会造成更严重的后果

我们可以注册一个信号处理函数,单独处理 SIGSEGV 信号:

void sigHandler(int sig)
{
    printf("Catched SIG_%d\n", sig);
    if (sig == SIGSEGV)
    {
        // 可以记录一下日志。。。

        exit(-1); // 如果是段错误,退出进程
    }
}

int main(void)
{
    signal(SIGSEGV, sigHandler);
    // ...
}

输出:

[root@localhost test]# ./test
Catched SIG_11