關注微信公眾號:CodingTechWork,一起學習進步。
引言
並發編程
並發編程的目的是為了改善串行程序執行慢問題,但是,並不是啟動更多線程就能夠讓程序執行更快。因為在並發時,容易受到軟硬件資源等限制,從而導致上下文切換慢,頻繁的上下文切換導致並發程序執行起來反而不如串行程序,違背了讓程序運行得更快一些這個最初的夢想。
單核和多核CPU
在介紹上下文切換前,我們先來了解一下計算機CPU的單核和多核概念。
最開始的單核CPU比較死腦,在通電時CPU就執行存儲塊中的指定地址的指令,如果想要執行內存塊其他地方的代碼必須調整總線位置才可以執行,這就阻塞程序了,期間只能執行某一個程序。后來,英特爾演進了一種叫做時間軸的工作方式,增加晶體管,CPU工作效率提高,並基於時鍾電路控制CPU跳轉到指定地址,借助操作系統來利用時鍾電路控制CPU的跳轉。
多核集成在一個芯片里,由多個CPU組成,多核通過內部總線交互和共享數據,其中會有一個核專門分配給操作系統用,由操作系統分配資源及控制CPU執行程序。
windows查看核數
打開命令行模式,可以利用Win+r
鍵打開運行,然后輸入cmd
。
除了直接使用systeminfo
命令,下面我們介紹wmic命令
來查看計算機相關信息。
- 輸入
wmic
,進入命令行環境 cpu get Name
:查看計算機的cpu信息cpu get NumberOfCores
:查看計算機的cpu核數cpu get NumberOfLogicalProcessors
:查看計算機的cpu線程數
C:\Users\Lenovo>wmic
wmic:root\cli>cpu get Name
Name
Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz
wmic:root\cli>cpu get NumberOfCores
NumberOfCores
6
wmic:root\cli>cpu get NumberOfLogicalProcessors
NumberOfLogicalProcessors
12
wmic:root\cli>
linux查看核數
一般linux的CPU相關信息都是存儲在/proc/cpuinfo
內,內存信息存儲在/proc/meminfo
中。
cat /proc/cpuinfo| grep "physical id"| sort| uniq| wc -l
:查看物理CPU個數cat /proc/cpuinfo| grep "cpu cores"| uniq
:查看單個CPU核數cat /proc/cpuinfo| grep "processor"| wc -l
:查看邏輯CPU個數cat /proc/meminfo| grep MemTotal
:查看內存大小
[linux@01 ~]$ cat /proc/cpuinfo| grep "cpu cores"| uniq
cpu cores : 1
[linux@01 ~]$ cat /proc/cpuinfo| grep "physical id"| sort| uniq| wc -l
16
[linux@01 ~]$ cat /proc/cpuinfo| grep "processor"| wc -l
16
[linux@01 ~]$ cat /proc/meminfo| grep MemTotal
MemTotal: 32780412 kB
上下文切換
上下文切換的介紹
我們對於單核和多核不陌生了,自己的電腦或者使用的機器總是希望核數越多越好,畢竟多核能夠支持更好的多線程並發。當然,單核處理器也支持多線程執行代碼,CPU是通過給每個線程分配CPU時間片
來實現。
時間片就是CPU分配給各個線程的時間,那單核是如何實現多線程並發執行的呢?那是因為時間片非常短(一般是幾十毫秒),CPU通過切換線程交替執行,給你一種錯覺好像是多個線程同時執行了。
CPU通過時間片來執行多線程執行,當前任務執行一個時間片后切換到下一個任務繼續執行,在切換任務前呢,其實CPU會保存該任務的狀態,這樣在下次切回該任務時,可以再加載這個任務的狀態,繼續執行(其實也就是保存當前線程的運行位置,同時呢,會加載需要恢復的線程環境相關信息。)。任務從保存到再加載的過程
,這就是一次上下文切換
的過程。
上下文切換一旦頻繁,必定消耗CPU資源,必定耗時,並發的性能將會受到影響。如何減少?
上下文切換的減少
減少上下文切換的方法有無鎖並發編程、CAS算法、使用最少線程和使用協程方式。
無鎖並發編程
:多線程競爭鎖時,大量的任務交替執行,會引起上下文切換,在多線程處理數據時,我們可以使用一些無鎖方式來處理共享數據,比如將數據的ID按照Hash算法取模進行分段,這樣不同的線程去處理不同分段的數據。CAS算法
:JAVA的Atomic包就使用了CAS算法來更新數據,無需加鎖。使用最少線程
:在程序開發過程中,要避免創建不必要的線程,如並發任務很少,若創建了很多線程來處理任務,可能在程序運行時,大部分線程都是出於等待閑置的狀態。比如減少線上大量的WAITING線程
,我們可以通過jstack [pid] > dumpName
將某個進程的線程信息dump到dumpName
文件中,然后通過篩選(grep java.lang.Thread.State dumpName | awk '{print $2$3$4$5}' | sort | uniq -c
)出多少個線程處於WAITING狀態。或者直接通過jstack -l [pid]| grep BLOCKED
、jstack -l [pid]| grep WAITING
等命令查看該進程的線程信息。協程
:在單線程里實現多個任務的調度,並在單個線程里面維持多個任務的切換。
上下文切換的查看
我們可以使用vmstat
命令和pidstat
命令查看上下文切換的次數等信息。
vmstat命令
vmstat
命令是Virtual Memory Statistics
虛擬內存統計的縮寫,可以對操作系統的虛擬內存、進程、CPU活動等進行監控統計。
vmstat命令參數詳解
[linux@01 ~]$ vmstat -help
Usage:
vmstat [options] [delay [count]]
Options:
-a, --active active/inactive memory
-f, --forks number of forks since boot
-m, --slabs slabinfo
-n, --one-header do not redisplay header
-s, --stats event counter statistics
-d, --disk disk statistics
-D, --disk-sum summarize disk statistics
-p, --partition <dev> partition specific statistics
-S, --unit <char> define display unit
-w, --wide wide output
-t, --timestamp show timestamp
-h, --help display this help and exit
-V, --version output version information and exit
For more details see vmstat(8).
比較常用的直接使用vmstat a
,其中a
代表的是多少秒實時顯示。
vmstat 實時統計整體情況
語法:
vmstat [count]
參數說明:
count
:表示統計間隔。
示例:
[linux@01 ~]$ vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
1 0 0 13223320 146732 1982108 0 0 0 3 0 0 0 0 99 0 0
1 0 0 13222504 146732 1982108 0 0 0 172 747 1224 0 0 99 0 0
0 0 0 13222696 146732 1982108 0 0 0 44 1078 1464 0 0 99 0 0
0 0 0 13223492 146732 1982108 0 0 0 0 749 1189 0 0 100 0 0
0 0 0 13223260 146732 1982092 0 0 0 16 789 1272 0 0 100 0 0
0 0 0 13223620 146732 1981996 0 0 0 0 1359 1589 0 0 99 0 0
0 0 0 13224364 146732 1981996 0 0 0 156 817 1279 0 0 100 0 0
0 0 0 13222488 146732 1981996 0 0 0 32 792 1292 0 0 100 0 0
pidstat命令
vmstat
可以對操作系統進行整體情況統計,但是無法對某個進程進行統計分析,下面學習一下pidstat
命令,可以針對整體或者某個進程進行分析。
pidstat安裝
pidstat命令
是對linux系統監控的一種命令,使用該命令可以對linux進程數據進行監控,可以輸出每個受內核管理的任務相關信息,而pidstat是sysstat
軟件套件的一部分,所以首先得安裝該命令。
如果是centos系統,使用yum install sysstat
安裝即可。
pidstat命令參數詳解
[linux@01 ~]$ pidstat -help
Usage: pidstat [ options ] [ <interval> [ <count> ] ]
Options are:
[ -d ] [ -h ] [ -I ] [ -l ] [ -r ] [ -s ] [ -t ] [ -U [ <username> ] ] [ -u ]
[ -V ] [ -w ] [ -C <command> ] [ -p { <pid> [,...] | SELF | ALL } ]
[ -T { TASK | CHILD | ALL } ]
其中:
interval
:表示間隔多久統計一次數據,可選的參數。count
:表示統計多少次,可選參數,若只傳了interval,而無count,則默認無限次統計次數。
pidstat cpu統計
語法:
- 全量:
pidstat -u [interval] [count]
或者默認pidstat [interval] [count]
- 某個任務:
pidstat -p <pid> [interval] [count]
全量任務cpu統計示例
[linux@01 ~]$ pidstat
Linux 3.10.0-514.el7.x86_64 (01) 03/09/2021 _x86_64_ (16 CPU)
08:45:48 AM UID PID %usr %system %guest %CPU CPU Command
08:45:48 AM 0 1 0.00 0.00 0.00 0.00 14 systemd
08:45:48 AM 0 2 0.00 0.00 0.00 0.00 15 kthreadd
08:45:48 AM 0 3 0.00 0.00 0.00 0.00 0 ksoftirqd/0
08:45:48 AM 0 7 0.00 0.00 0.00 0.00 0 migration/0
08:45:48 AM 0 9 0.00 0.01 0.00 0.01 15 rcu_sched
08:45:48 AM 0 10 0.00 0.00 0.00 0.00 0 watchdog/0
08:45:48 AM 0 11 0.00 0.00 0.00 0.00 1 watchdog/1
08:45:48 AM 0 12 0.00 0.00 0.00 0.00 1 migration/1
08:45:48 AM 0 13 0.00 0.00 0.00 0.00 1 ksoftirqd/1
某個pid任務cpu統計示例
[linux@01 ~]$ pidstat -u -p 27680 1
Linux 3.10.0-514.el7.x86_64 (01) 03/09/2021 _x86_64_ (16 CPU)
09:14:19 AM UID PID %usr %system %guest %CPU CPU Command
09:14:20 AM 1000 27680 1.00 0.00 0.00 1.00 8 java
09:14:21 AM 1000 27680 0.00 0.00 0.00 0.00 8 java
09:14:22 AM 1000 27680 0.00 0.00 0.00 0.00 8 java
09:14:23 AM 1000 27680 0.00 0.00 0.00 0.00 8 java
統計參數詳解:
UID
:表示監視任務的真實用戶標識編號。PID
:表示被監控任務的進程號%usr
:表示該任務在用戶態
應用程序執行時的CPU使用率,該字段的CPU計算時間不包括在虛擬處理器中花去的時間。(用戶層面)%system
:表示該任務在內核態
執行時的CPU使用率。(系統層面)%guest
:表示該任務在虛擬機處理器
上執行時的CPU使用率。(虛擬層面)。%CPU
:表示該任務總的CPU使用率。在多處理器環境中,如果帶上-I
參數,CPU使用率的計算會除以機器的CPU數量。CPU
:表示正在運行該任務的處理器編號。Command
:表示該任務的執行命令名稱。
pidstat I/O統計
語法:
pidstat -d -p <pid>
示例:
[linux@01 ~]$ pidstat -d -p 27680
Linux 3.10.0-514.el7.x86_64 (01) 03/09/2021 _x86_64_ (16 CPU)
08:52:55 AM UID PID kB_rd/s kB_wr/s kB_ccwr/s Command
08:52:55 AM 1000 27680 0.00 0.01 0.00 java
統計參數詳解:
UID
:表示監視任務的真實用戶標識編號。PID
:表示被監控任務的進程號kB_rd/s
:表示該任務從硬盤上的讀取速度,rd
表示read
。kB_wr/s
:表示該任務向硬盤中的寫入速度,wr
表示write
。kB_ccwr/s
:表示該任務寫入磁盤被取消的速度。ccwr
表示cancel write
。Command
:表示該任務的執行命令名稱。
pidstat 內存統計
語法:
pidstat -r -p <pid>
示例:
[linux@01 ~]$ pidstat -r -p 27680
Linux 3.10.0-514.el7.x86_64 (01) 03/09/2021 _x86_64_ (16 CPU)
08:56:48 AM UID PID minflt/s majflt/s VSZ RSS %MEM Command
08:56:48 AM 1000 27680 0.01 0.00 13124360 2489432 7.59 java
統計參數詳解:
UID
:表示監視任務的真實用戶標識編號。PID
:表示被監控任務的進程號。minflt/s
:表示從內存中加載數據時每秒出現的較小錯誤數目,這些不要求從磁盤載入內存頁面,其中minflt
表示minor fault
。majflt/s
:表示從內存中加載數據時每秒出現的較大錯誤數目,這些要求從磁盤載入內存頁面,其中majflt
表示major fault
。VSZ
:表示虛擬容量,整個進程的虛擬內存使用(單位:kb)RSS
:表示長期內存使用,任務的不可交換物理內存的使用量(單位:kb)Command
:表示該任務的執行命令名稱。
pidstat 間隔有限次數監控
語法:
pidstat -p <pid> [interval] [count]
示例
[linux@01 ~]$ pidstat -p 27680 1 5
Linux 3.10.0-514.el7.x86_64 (01) 03/09/2021 _x86_64_ (16 CPU)
09:03:55 AM UID PID %usr %system %guest %CPU CPU Command
09:03:56 AM 1000 27680 0.00 0.00 0.00 0.00 8 java
09:03:57 AM 1000 27680 0.00 0.00 0.00 0.00 8 java
09:03:58 AM 1000 27680 1.00 0.00 0.00 1.00 8 java
09:03:59 AM 1000 27680 0.00 0.00 0.00 0.00 8 java
09:04:00 AM 1000 27680 0.00 0.00 0.00 0.00 8 java
Average: 1000 27680 0.20 0.00 0.00 0.20 - java
pidstat 上下文切換統計
語法:
pidstat -w <pid> [interval] [count]
示例
[linux@01 ~]$ pidstat -w -p 27680
Linux 3.10.0-514.el7.x86_64 (01) 03/09/2021 _x86_64_ (16 CPU)
09:11:15 AM UID PID cswch/s nvcswch/s Command
09:11:15 AM 1000 27680 0.00 0.00 java
[linux@01 ~]$ pidstat -w -p 27680 1 10
Linux 3.10.0-514.el7.x86_64 (01) 03/09/2021 _x86_64_ (16 CPU)
09:11:39 AM UID PID cswch/s nvcswch/s Command
09:11:40 AM 1000 27680 0.00 0.00 java
09:11:41 AM 1000 27680 0.00 0.00 java
09:11:42 AM 1000 27680 0.00 0.00 java
09:11:43 AM 1000 27680 0.00 0.00 java
09:11:44 AM 1000 27680 0.00 0.00 java
09:11:45 AM 1000 27680 0.00 0.00 java
09:11:46 AM 1000 27680 0.00 0.00 java
09:11:47 AM 1000 27680 0.00 0.00 java
09:11:48 AM 1000 27680 0.00 0.00 java
09:11:49 AM 1000 27680 0.00 0.00 java
Average: 1000 27680 0.00 0.00 java
統計參數詳解:
UID
:表示監視任務的真實用戶標識編號。PID
:表示被監控任務的進程號。cswch/s
:表示該任務每秒自願上下文切換次數,當某一任務處於阻塞等待時,將主動讓出自己的CPU資源。其中cswch
表示context switch
。nvcswch/s
:表示該任務每秒非資源上下文切換次數,CPU分配給某一任務的時間片已經用完,因此將強迫該進程讓出CPU的執行權。其中nvcswch
表示involuntary context switch
Command
:表示該任務的執行命令名稱。
REF《Java並發編程的藝術》