线程数量统计

快速起步

在系统监控时,我们需要关注一个进程的线程数量以及操作系统的线程总量,可以采用如下简便的方法:

  • 获取一个指定 pid 的所有线程数量:

获取指定pid的所有线程的数量
ps -o nlwp <pid>

注意,这里会直接返回一个进程的所有线程总数(直接返回数字),原因是这里参数 nlwp 表示 Number of LightWeight Processes ,也就是线程数量

举例 我的 KVM Atlas 虚拟机 z-b-data-1 的进程PID是 7410 ,则可以通过以下命令检查:

ps -o nlwp 7410

输出结果:

9

表明有9个线程

其实,这个线程数量可以从 /proc/<pid>/status 中查看:

cat /proc/7410/status

输出案例:

检查进程对应的状态(线程数量)
Name:	qemu-system-x86
Umask:	0002
State:	S (sleeping)
Tgid:	7410
Ngid:	7418
Pid:	7410
PPid:	1
TracerPid:	0
Uid:	64055	64055	64055	64055
Gid:	108	108	108	108
FDSize:	128
Groups:	20 108 64055
NStgid:	7410
NSpid:	7410
NSpgid:	7409
NSsid:	7409
VmPeak:	17678804 kB
VmSize:	17675976 kB
VmLck:	16777088 kB
VmPin:	       0 kB
VmHWM:	16838564 kB
VmRSS:	16825184 kB
RssAnon:	16805424 kB
RssFile:	   19756 kB
RssShmem:	       4 kB
VmData:	17135716 kB
VmStk:	     136 kB
VmExe:	    6084 kB
VmLib:	   13916 kB
VmPTE:	   33600 kB
VmSwap:	       0 kB
HugetlbPages:	       0 kB
CoreDumping:	0
THP_enabled:	1
Threads:	9
SigQ:	0/1546799
SigPnd:	0000000000000000
ShdPnd:	0000000000000000
SigBlk:	0000000010002240
SigIgn:	0000000000001000
SigCgt:	0000000100004243
CapInh:	0000000000000000
CapPrm:	0000000000000000
CapEff:	0000000000000000
CapBnd:	000001ffffffffff
CapAmb:	0000000000000000
NoNewPrivs:	1
Seccomp:	2
Seccomp_filters:	1
Speculation_Store_Bypass:	thread force mitigated
SpeculationIndirectBranch:	conditional force disabled
Cpus_allowed:	ffff,ffffffff
Cpus_allowed_list:	0-47
Mems_allowed:	00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000003
Mems_allowed_list:	0-1
voluntary_ctxt_switches:	32359118
nonvoluntary_ctxt_switches:	66982
  • 获取整个操作系统的线程数量(非常有用的监控命令):

获取Linux操作系统所有线程总数
ps -eo nlwp | tail -n +2 | awk '{ num_threads += $1 } END { print num_threads }'

排查线程数过度问题

生产环境出现线程过多问题,检查是哪个进程导致线程过多:

  • 获取系统消耗线程最多的进程:

获取Linux操作系统消耗最多线程的进程
ps -eo pid,command,nlwp | sort -n -k3
# 或者
ps axo pid,ppid,nlwp,cmd | sort -n -k3
获取Linux操作系统消耗最多线程的进程
...
198370 /usr/local/bin/containerd-s  208
413302 /usr/local/bin/containerd-s  352
 73275 sleep 86400                    1
 378199 /usr/local/bin/containerd-s 149804

这个命令不太完善,不过可以看到 378199 进程消耗了过多的线程。实际上在 top检查线程数量 也看到了这个消耗过多的进程:

topnTH 字段无法显示超过3位数值
top - 16:02:21 up 405 days,  5:56,  1 user,  load average: 100.40, 94.44, 95.02
Tasks: 1654 total,   2 running, 1017 sleeping,   0 stopped,  19 zombie
%Cpu(s): 57.6 us, 15.1 sy,  0.0 ni, 27.3 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem : 79093344+total, 22562152 free, 34725468 used, 73364582+buff/cache
KiB Swap:        0 total,        0 free,        0 used. 52038676 avail Mem

   PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                                                                              nTH
378199 root      20   0  355.1g   6.5g   5.6g S  66.5  0.9 343573:12 rund-c8fe00be                                                                        14+
413302 root      20   0  375.5g 350.1g 350.0g S  2654 46.4 262097,00 rund-10930310                                                                        352
423338 root      20   0   10.7g 166248  53324 S  13.0  0.0   4787:05 containerd                                                                           236
198370 root      20   0   31.2g   7.3g   7.2g S 110.8  1.0 470711:52 rund-822bb8d1                                                                        205
405808 root      20   0  168512  45700  10036 S   0.0  0.0 813:53.55 node_agent_k8s                                                                       168
...
  • (推荐) ps 命令可以检查指定进程的线程,非常重要的命令:

检查指定进程的线程 重要命令
ps -T -p <PID>

输出显示类似:

检查指定进程的线程输出案例
   PID   SPID TTY          TIME CMD
 39112  39112 ?        00:00:00 rund-1f1b78d6
 39112  39115 ?        00:00:00 tokio-runtime-w
 39112  40565 ?        3-11:35:27 vmm_master
 39112  41223 ?        00:00:00 blk_iothread_q0
 39112  43205 ?        39-17:59:40 fc_vcpu0
 39112  43206 ?        34-22:50:52 fc_vcpu1
 39112  43207 ?        34-23:18:56 fc_vcpu2
...

可以看到,这里根据第5列 线程命令 进行统计,就能找出哪个命令大量出现线程泄漏:

统计指定进程的哪个线程出现泄
ps -T -p <PID> | awk '{print $5}' | sort | uniq -c | sort -n -k1

输出类似:

统计指定进程的哪个线程出现泄
...
      3 listener_loop
      3 reaper
      3 rund-1f1b78d6
      4 prealloc-memnum
  23758 client_handler

debug线程数量问题

根据找到的怀疑泄漏线程的命令,例如上文 client_handler ,我们可以找一下这个问题线程的堆栈是否有异常:

检查异常线程的堆栈
#cat /proc/303564/stack
[<0>] futex_wait_queue_me+0xb6/0x110
[<0>] futex_wait+0xe9/0x240
[<0>] do_futex+0xa7/0x150
[<0>] __x64_sys_futex+0x146/0x1c0
[<0>] do_syscall_64+0x2d/0x40
[<0>] entry_SYSCALL_64_after_hwframe+0x44/0xa9

可以看到陷入了一个 syscall

进程允许的最大线程数量

  • 操作系统级别允许每个进程 clone() 的线程数量可以从 procfs 获取:

通过 procfs 检查操作系统允许每个进程的最大线程数量
cat /proc/sys/kernel/threads-max

在我的 Ubuntu Linux 22.04 上,默认每个进程最多允许大约 31w 线程

通过 procfs 可以看到操作系统允许每个进程最多31w线程
3093599
  • 此外,可以通过 ulimits 检查每个用户允许发起的进程数量:

通过 ulimits -a 可以检查当前用户允许的最大进程数量
ulimit -a | grep -i processes

可以看到每个用户允许的进程数量恰好是每个进程允许线程数量的一半,即 15.5w 进程:

通过 ulimits -a 查当前用户允许的最大进程数量大约是 15.5w
max user processes                  (-u) 1546799

备注

根据操作系统允许的每个用户的最大进程数量 15.5w ,乘以操作系统允许每个进程的最大线程数量 31w ,实际上每一个用户能够在操作系统发起的线程数量是惊人的 4.785 万亿个线程,差不多 接近5万亿线程 。不过,实际上,海量的线程会导致系统运行缓慢,所以我们需要在进程出现线程大量堆积的时候,及时排查故障解决软件bug。

  • 操作系统级别允许的进程数量也可以从 procfs 中获取:

通过 procfs 检查操作系统允许的进程数量
cat /proc/sys/kernel/pid_max

在我的 Ubuntu Linux 22.04 上,默认操作系统允许最大进程数量大约是 42w 进程:

通过 procfs 检查操作系统默认允许的进程总量大约是42w
4194304

该参数可以调整:

通过 sysctl 修改操作系统最大允许进程数量,例如修改成6.5w
echo kernel.pid_max = 65534 >> /etc/sysctl.conf
sysctl -p

参考