中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

一個GO語言性能問題的發現和解決

發布時間:2020-06-29 06:28:00 來源:網絡 閱讀:227 作者:UCloud_TShare 欄目:云計算

本文是大 U 同事的一篇實操性經驗貼,是發現問題、分析問題到解決問題的完整案例,借此分享,希望對各位有所幫助。

事件起因

事情起因于公司一位同事在內部郵件組中 post 了一個問題,一個使用了 go1.8.3 寫的業務程序跑了一段時間后出現部分 goroutine 卡在等待一個鎖 ForkLock 的現象,同事認為這是 go1.8.3 的 bug,升級到 go1.10 后沒有再重現。為了搞清楚這個事情,同事在 github 上發了 issue:

https://github.com/golang/go/issues/26836,期間也做了很多重現的嘗試,但并未重現。

我瀏覽了一下出現該問題的業務代碼,大概的使用方式是父進程調用 os/exec 下的 Command 開子進程執行 shell 命令。Command 后面會調用 golang 封裝的 forkExec 來開子進程并執行命令,forkExec 使用了 ForkLock。

問題分析

ForkLock 的存在是為了避免下面的情況:在有多個 goroutine 同時 fork exec 的情況下, 為了子進程只繼承它需要的文件描述符,需要在父進程在創建這些文件描述符的時候加上 O_CLOEXEC 標志,這樣在子進程中這些描述符是關閉的,子進程按需把自己需要繼承的描述符打開即可。

Linux 在 2.6.27 之后,打開文件或者管道,和設置 O_CLOEXEC 是一個原子操作,因此問題不大,但 golang 對內核版本的要求是 2.6.23 及以上,另外 Unix 系統中,open 和設置 O_CLOEXEC 是兩個操作,如果在兩個操作之間發生 fork, 子進程就可能繼承它不需要的文件描述符,因此需要加鎖。重點看下 forkExec 時候的源代碼:

一個GO語言性能問題的發現和解決

從問題的現象看,肯定是某 goroutine 在 forkExecPipe 或者 forkAndExecInChild 這兩步卡住了,鎖沒釋放,因此有些 goroutine 一直拿不到鎖,饑餓致死。forkExecPipe 最后調用的是內核 pipe2,forkAndExecInChild 最后調用的是內核 clone 和 exec。

原因猜測

pipe2 是一個快速系統調用,因此可能 block 的系統調用是 clone 和 exec, 加上在 go1.10 上這個問題沒有重現,對比 go1.8 代碼和 go1.9 在 forkAndExecInChild 函數上的差異:

go1.8

一個GO語言性能問題的發現和解決

go1.9

一個GO語言性能問題的發現和解決

go1.9 增加了 CLONE_VFORK 和 CLONE_VM。只帶 SIGCHILD 的 clone 可以認為類似于 fork(最后都是調用 do_fork), fork 的問題是,在父進程占用內存越大性能越差,具體可以看這個鏈接:

https://bugzilla.redhat.com/show_bug.cgi?id=682922

這個 case 2011 年提出,今年 7 月還在更新,這個 case 反饋的問題是,盡管 Linux kernel 引入 copy-on-write 機制,但 fork 的時候依然要拷貝頁表項,進程虛擬內存越大,需要拷貝的頁表項越多,因此 fork 越慢。Golang 的討論組有人測試過,heap size 在 2G 的情況下,fork 耗時可以到毫秒級別, 正常是及幾十微秒,上千倍差距。

Go1.9 加上這兩個參數是為了讓子進程和父進程共享內存,相當于調用 vfork, 不需要拷貝頁表項, 加快創建速度,從測試效果看,穩定在幾十微妙。

一個GO語言性能問題的發現和解決

所以一個合理的猜測是,在低于 go1.9 版寫的程序中,當程序內存占用足夠大,而且創建進程頻率足夠頻繁,會導致 ForkLock 長時間等待。

實驗論證

一個GO語言性能問題的發現和解決

我用 go1.8.3 寫了一個測試程序,在 2 核 4G 的虛擬機(kernel 3.10.0-693.17.1.el7.x86_64)下測試。

在外部每隔 10 秒,給這個程序發 SIGUSR1 信號,打印運行時堆棧,運行一段時間后,部分 goroutine 獲取 ForkLock 的時間越來越長。見下面兩圖:

一個GO語言性能問題的發現和解決

一個GO語言性能問題的發現和解決

而在 go1.9 及以上版本上并未出現上述情況,這個結果我覺得已經可以說明問題。升級版本到 go1.9 及以上版本可以解決該問題。

寫在最后

vfork 是為了解決 fork 拷貝頁表項導致的性能問題, 而且大部分場景 fork 之后是調用 exec,exec 要把所有頁表刪除重置新的頁表, 實在沒必要再拷貝頁表項。但由于 vfork 父子進程共享內存,所以使用要很小心,如果子進程修改某個變量,會影響到父進程,而且 kernel 會掛起父進程,讓子進程先執行,這些限制基本限制 vfork 只適合跟 exec 的場景,不如 fork 通用。

正因為 vfork 的使用需要小心,因此 go1.9 準備加入 vfork 發布之前,有人提出代碼不夠健壯,因為 rawVforkSyscall 返回之后,在父進程段還執行指令,這樣子進程有機會破壞雙方的共享棧,因此提了一個 commit 去讓 rawVforkSyscall 在返回后,在父進程段什么都不做直接 return,解決這個互相影響,如圖所示:

一個GO語言性能問題的發現和解決

如有興趣深入了解,可以看下這個 commit 的 review,Rob Pike 等人都有發言。

https://go-review.googlesource.com/c/go/+/46173

一個GO語言性能問題的發現和解決

一個GO語言性能問題的發現和解決

更多技術干貨,請關注?“云計算總動員”?,我們一起在這里,用云計算改變未來。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

达日县| 惠州市| 亚东县| 淮南市| 咸丰县| 教育| 沙坪坝区| 博爱县| 盘锦市| 永兴县| 镇赉县| 泗水县| 喜德县| 寿阳县| 云和县| 武隆县| 沁水县| 庐江县| 雷州市| 洪江市| 隆昌县| 剑川县| 囊谦县| 福贡县| 宣恩县| 旌德县| 丹东市| 共和县| 普定县| 闽清县| 沙田区| 浠水县| 泰州市| 广东省| 神木县| 合江县| 林甸县| 红原县| 安康市| 扎鲁特旗| 光山县|