线程数量统计¶
快速起步¶
在系统监控时,我们需要关注一个进程的线程数量以及操作系统的线程总量,可以采用如下简便的方法:
获取一个指定
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
获取整个操作系统的线程数量(非常有用的监控命令):
ps -eo nlwp | tail -n +2 | awk '{ num_threads += $1 } END { print num_threads }'
排查线程数过度问题¶
生产环境出现线程过多问题,检查是哪个进程导致线程过多:
获取系统消耗线程最多的进程:
ps -eo pid,command,nlwp | sort -n -k3
# 或者
ps axo pid,ppid,nlwp,cmd | sort -n -k3
...
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检查线程数量 也看到了这个消耗过多的进程:
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
获取:
cat /proc/sys/kernel/threads-max
在我的 Ubuntu Linux 22.04 上,默认每个进程最多允许大约 31w
线程
3093599
此外,可以通过
ulimits
检查每个用户允许发起的进程数量:
ulimit -a | grep -i processes
可以看到每个用户允许的进程数量恰好是每个进程允许线程数量的一半,即 15.5w
进程:
max user processes (-u) 1546799
备注
根据操作系统允许的每个用户的最大进程数量 15.5w
,乘以操作系统允许每个进程的最大线程数量 31w
,实际上每一个用户能够在操作系统发起的线程数量是惊人的 4.785
万亿个线程,差不多 接近5万亿线程 。不过,实际上,海量的线程会导致系统运行缓慢,所以我们需要在进程出现线程大量堆积的时候,及时排查故障解决软件bug。
操作系统级别允许的进程数量也可以从
procfs
中获取:
cat /proc/sys/kernel/pid_max
在我的 Ubuntu Linux 22.04 上,默认操作系统允许最大进程数量大约是 42w
进程:
4194304
该参数可以调整:
echo kernel.pid_max = 65534 >> /etc/sysctl.conf
sysctl -p