在平時工作中,經常會聽到應用程式的程式和執行緒的概念,那麼它們兩個之間究竟有什麼關係或不同呢?
一、對比程式和執行緒
1)兩者概念
- 程式是具有一定獨立功能的程式關於某個資料集合上的一次執行活動,程式是系統進行資源分配和排程的一個獨立單位.
- 執行緒是指程式內的一個執行單元,也是程式內的可排程實體. 執行緒是CPU排程和分派的基本單位,它是比程式更小的能獨立執行的基本單位執行緒自己基本上不擁有系統資源,只擁有一點在執行中必不可少的資源(如程式計數器,一組暫存器和棧),但是它可與同屬一個程式的其他的執行緒共享程式所擁有的全部資源.
2)兩者關係
- 一個執行緒可以建立和撤銷另一個執行緒;同一個程式中的多個執行緒之間可以併發執行.
- 相對程式而言,執行緒是一個更加接近於執行體的概念,它可以與同程式中的其他執行緒共享資料,但擁有自己的棧空間,擁有獨立的執行序列。
3)兩者區別
- 程式和執行緒的主要差別在於它們是不同的作業系統資源管理方式:程式有獨立的地址空間,一個程式崩潰後,在保護模式下不會對其它程式產生影響;而執行緒只是一個程式中的不同執行路徑。
- 執行緒有自己的堆疊和區域性變數,但執行緒之間沒有單獨的地址空間,一個執行緒死掉就等於整個程式死掉,所以多程式的程式要比多執行緒的程式健壯,但在程式切換時,耗費資源較大,效率要差一些. 但對於一些要求同時進行並且又要共享某些變數的併發操作,只能用執行緒,不能用程式。
程式和執行緒的區別
- 地址空間:執行緒是程式內的一個執行單元;程式至少有一個執行緒;它們共享程式的地址空間;而程式有自己獨立的地址空間;
- 資源擁有:程式是資源分配和擁有的單位,同一個程式內的執行緒共享程式的資源
- 執行緒是處理器排程的基本單位,但程式不是.
- 程式和執行緒二者均可併發執行.
- 簡而言之,一個程式至少有一個程式,一個程式至少有一個執行緒.
- 執行緒的劃分尺度小於程式,使得多執行緒程式的併發性高。
- 另外,程式在執行過程中擁有獨立的記憶體單元,而多個執行緒共享記憶體,從而極大地提高了程式的執行效率。
- 執行緒在執行過程中與程式是有區別的。每個獨立的執行緒有一個程式執行入口、順序執行序列和程式的出口。但是執行緒不能夠獨立執行,必須依存在應用程式中,由應用程式提供多個執行緒執行控制。
- 從邏輯角度來看,多執行緒的意義在於一個應用程式中,有多個執行部分可以同時執行。但作業系統並沒有將多個執行緒看做多個獨立的應用,來實現程式的排程和管理以及資源分配。這就是程式和執行緒的重要區別。
4)優缺點
執行緒和程式在使用上各有優缺點:
- 執行緒執行開銷小,但不利於資源的管理和保護;而程式正相反。
- 執行緒適合於在SMP機器上(即對稱多處理結構的簡稱,是指在一個計算機上彙集了一組處理器(多CPU),各CPU之間共享記憶體子系統以及匯流排結構)執行,而程式則可以跨機器遷移。
二、如何檢視某個程式的執行緒數
有些時候需要確定程式內部當前執行了多少執行緒,查詢方法如下: 1)通過pstree命令(根據pid)進行查詢: [root@xqsj_web2 ~]# ps -ef|grep java //查詢程式pid(比如這裡查詢java(tomcat)程式的pid) [root@xqsj_web2 ~]# pstree -p 19135 java(19135)─┬─{java}(19136) ├─{java}(19137) ....... └─{java}(13578) [root@xqsj_web2 ~]# pstree -p 19135|wc -l 46 //由於第一行包括了2個執行緒,所以該程式下一共有47個執行緒! 或者使用top命令檢視(可以檢視到執行緒情況) [root@xqsj_web2 ~]# top -Hp 19135 //下面結果中的Tasks 對應的47即是執行緒的個數 top - 14:05:55 up 391 days, 20:59, 1 user, load average: 0.00, 0.00, 0.00 Tasks: 47 total, 0 running, 47 sleeping, 0 stopped, 0 zombie Cpu(s): 0.2%us, 0.1%sy, 0.0%ni, 99.7%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st Mem: 8058056k total, 7718656k used, 339400k free, 354216k buffers Swap: 0k total, 0k used, 0k free, 4678160k cached PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 19135 root 20 0 5339m 632m 5476 S 0.0 8.0 0:00.00 java 19136 root 20 0 5339m 632m 5476 S 0.0 8.0 0:00.84 java ...... 2)根據ps命令直接查詢: [root@xqsj_web2 ~]# ps hH p 19135| wc -l 47 3)通過檢視/proc/pid/status proc偽檔案系統,它駐留在/proc目錄,這是最簡單的方法來檢視任何活動程式的執行緒數。/proc目錄以可讀文字檔案形式輸出,提供現有程式和系統硬體 相關的資訊如CPU、中斷、記憶體、磁碟等等。 [root@xqsj_web2 ~]# cat /proc/19135/status Name: java State: S (sleeping) Tgid: 19135 Pid: 19135 PPid: 1 TracerPid: 0 ........ Threads: 47 //這裡顯示的是程式建立的匯流排程數。輸出表明該程式有47個執行緒。 SigQ: 1/62793 SigPnd: 0000000000000000 ShdPnd: 0000000000000000 ....... voluntary_ctxt_switches: 1 nonvoluntary_ctxt_switches: 1 或者,也可以在/proc//task中簡單的統計子目錄的數量,如下所示: [root@xqsj_web2 ~]# ll /proc/19135/task 總用量 0 dr-xr-xr-x 6 root root 0 6月 14 17:57 11553 ...... [root@xqsj_web2 ~]# ll /proc/19135/task|wc -l 48 這是因為,對於一個程式中建立的每個執行緒,在/proc/<pid>/task中會建立一個相應的目錄,命名為其執行緒ID。由此在/proc/<pid>/task中目錄的總數表示在程式中執行緒的數目。
比如某臺伺服器的CPU使用率飆升,通過top命令檢視是gitlab程式佔用的cpu比較大,"ps -ef|grep gitlab"發現有很多個gitlab程式,現在需要查詢gitlab各個程式下的執行緒數情況。批量查詢命令如下:
# for pid in $(ps -ef|grep -v grep|grep gitlab|awk '{print $2}');do echo ${pid} > /root/a.txt ;cat /proc/${pid}/status|grep Threads > /root/b.txt;paste /root/a.txt /root/b.txt;done|sort -k3 -rn
指令碼解釋:
1)for pid in $(ps -ef|grep -v grep|grep gitlab|awk '{print $2}') 定義${pid}變數為gitlab程式的pid號 2)echo ${pid} > /root/a.txt 將http程式的pid號都列印到/root/a.txt檔案中 3)cat /proc/${pid}/status|grep Threads > /root/b.txt 將各個pid程式號下的執行緒資訊列印到/root/b.txt檔案中 4)paste /root/a.txt /root/b.txt 以列的形式展示a.txt和b/txt檔案中的資訊 5)sort -k3 -rn -k3 表示以第三列進行排序 -rn 表示降序
來看個cup使用率告警問題處理案例
收到告警,生產環境一臺機器的cpu使用率超過了85%!立刻登入伺服器,發現情況如下: [root@kevin ~]# uptime 10:39:40 up 215 days, 13:02, 2 users, load average: 3.32, 3.40, 3.37 [root@kevin ~]# top top - 10:52:51 up 215 days, 13:15, 3 users, load average: 3.32, 3.40, 3.37 Tasks: 168 total, 1 running, 167 sleeping, 0 stopped, 0 zombie Cpu(s): 98.4%us, 0.2%sy, 0.0%ni, 1.3%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st Mem: 8053692k total, 6542296k used, 1511396k free, 168560k buffers Swap: 16777212k total, 0k used, 16777212k free, 2565452k cached PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 31969 app 20 0 4510m 1.9g 6220 S 393.5 25.1 2281:49 java ......... [root@kevin ~]# ps -ef|grep 31969 root 15826 15129 0 10:58 pts/0 00:00:00 grep 31969 app 31969 31962 0 Jul02 ? 02:25:01 java -cp /data/lo-boxes/box_home/lo-starter.jar:/data/lo-boxes/box_home/lib/* -Dbox.name=B0002 -Dbox.home=/data/lo-boxes/B0002 -Dbox.jmx.port=57009 -XX:+CMSPermGenSweepingEnabled -XX:SoftRefLRUPolicyMSPerMB=1 -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=60 -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+UseParNewGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=1 -XX:+CMSClassUnloadingEnabled -XX:MaxTenuringThreshold=12 -XX:SurvivorRatio=8 -XX:ParallelGCThreads=3 -XX:+HeapDumpOnOutOfMemoryError -Dsun.reflect.inflationThreshold=2147483647 -XX:HeapDumpPath=/data/lo-boxes/B0002/boxlogs/logs/heapdump_31961.hprof -Xloggc:/data/lo-boxes/B0002/boxlogs/gclogs/gc.31961.log -XX:ErrorFile=/data/lo-boxes/B0002/boxlogs/hs_err_pid31961.log -Xms1024M -Xmx1024M -XX:PermSize=256M -XX:MaxPermSize=256M -XX:NewSize=256M -XX:MaxNewSize=512M cn.openlo.starter.BoxStartupStandalone 解決辦法: 1)最簡單粗暴的方法:重啟上面這個pid號為31969的程式所屬的服務程式 2)查出這個pid程式的cpu資源各自被哪個執行緒所佔。通過"top -Hp pid"可以檢視該程式下各個執行緒的cpu使用情況;如下: [root@kevin ~]# top -Hp 31969 ....... PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 31969 app 20 0 3754m 1714m 16m S 390.5 29.1 0:00.00 java 31970 app 20 0 3754m 2124m 16m S 382.2 20.5 0:02.74 java 31971 app 20 0 3754m 1954m 16m S 360.0 19.5 0:00.49 java 31972 app 20 0 3754m 1584m 16m S 300.9 23.1 0:00.48 java ...... 如上可知,31969的程式主要被上面四個執行緒耗了過多的CPU資源。 通過top命令定位到cpu佔用率較高的執行緒之後,繼續使用jstack pid命令檢視當前java程式的堆疊狀態,這就用到jstack工具! jstack是java虛擬機器自帶的一種堆疊跟蹤工具。jstack用於列印出給定的java程式ID或core file或遠端除錯服務的Java堆疊資訊。 jstack可以定位到執行緒堆疊,根據堆疊資訊我們可以定位到具體程式碼,所以它在JVM效能調優中使用得非常多。 jstack工具一般是在java/bin目錄下的。如下設定java環境變數 ============================================================================================= [root@kevin ~]# ll /data/software/ 總用量 626244 drwxr-xr-x 8 app app 4096 4月 11 2015 jdk1.7.0_80 -rw-r--r-- 1 app app 153530841 6月 4 2016 jdk-7u80-linux-x64.tar.gz [root@kevin ~]# /data/software/jdk1.7.0_80/bin/java -version java version "1.7.0_80" Java(TM) SE Runtime Environment (build 1.7.0_80-b15) Java HotSpot(TM) 64-Bit Server VM (build 24.80-b11, mixed mode) [root@kevin ~]# vim /etc/profile JAVA_HOME=/data/software/jdk1.7.0_80 JAVA_BIN=/data/software/jdk1.7.0_80/bin PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin:/bin:/sbin/:/data/software/jdk1.7.0_80/bin/ CLASSPATH=.:/lib/dt.jar:/lib/tools.jar export JAVA_HOME JAVA_BIN PATH CLASSPATH [root@kevin ~]# source /etc/profile [root@kevin ~]# mv /usr/bin/java /usr/bin/java.bak [root@kevin ~]# ln -s /data/software/jdk1.7.0_80/bin/java /usr/bin/java [root@kevin ~]# java -version java version "1.7.0_80" Java(TM) SE Runtime Environment (build 1.7.0_80-b15) Java HotSpot(TM) 64-Bit Server VM (build 24.80-b11, mixed mode) [root@kevin ~]# jstack --help Usage: jstack [-l] <pid> (to connect to running process) jstack -F [-m] [-l] <pid> (to connect to a hung process) jstack [-m] [-l] <executable> <core> (to connect to a core file) jstack [-m] [-l] [server_id@]<remote server IP or hostname> (to connect to a remote debug server) Options: -F to force a thread dump. Use when jstack <pid> does not respond (process is hung) -m to print both java and native frames (mixed mode) -l long listing. Prints additional information about locks -h or -help to print this help message ============================================================================================= 下面開始使用jstack對 [root@kevin ~]# jstack 31969 或者"jstack 31969 > jstack-31969" 列印出堆疊資訊到一個檔案中,方便後續檢視 [root@kevin ~]# jstack 31970 [root@kevin ~]# jstack 31971 [root@kevin ~]# jstack 31972
jstack命令生成的thread dump資訊包含了JVM中所有存活的執行緒,為了分析指定執行緒,必須找出對應執行緒的呼叫棧,應該如何找?
在top命令中,已經獲取到了佔用cpu資源較高的執行緒pid,將該pid轉成16進位制的值,在thread dump中每個執行緒都有一個nid,找到對應的nid即可;隔段時間再執行一次stack命令獲取thread dump,區分兩份dump是否有差別,在nid=0x246c的執行緒呼叫棧中,發現該執行緒一直在執行JstackCase類第33行的calculate方法,得到這個資訊,就可以檢查對應的程式碼是否有問題。
獲取程式pid的方法 [root@ansible-server ~]# ps -ef|grep nginx root 2148 1 0 2018 ? 00:00:00 nginx: master process /usr/sbin/nginx -c /etc/nginx/nginx.conf root 16517 16179 0 11:09 pts/1 00:00:00 grep nginx nginx 21091 2148 0 Jan27 ? 00:00:00 nginx: worker process 使用"ps x" [root@ansible-server ~]# ps x | grep nginx 2148 ? Ss 0:00 nginx: master process /usr/sbin/nginx -c /etc/nginx/nginx.conf 16526 pts/1 S+ 0:00 grep nginx [root@ansible-server ~]# ps x | grep nginx|grep -v grep|awk '{print $1}' 2148 使用"pgrep" [root@ansible-server ~]# pgrep nginx 2148 21091 [root@ansible-server ~]# pgrep -f nginx 2148 21091 使用pidof [root@ansible-server ~]# pidof nginx 21091 2148 使用pstree [root@ansible-server ~]# pstree -p | grep nginx |-nginx(2148)---nginx(21091)