真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

一個(gè)GO語言性能問題的發(fā)現(xiàn)和解決

本文是大 U 同事的一篇實(shí)操性經(jīng)驗(yàn)貼,是發(fā)現(xiàn)問題、分析問題到解決問題的完整案例,借此分享,希望對各位有所幫助。

創(chuàng)新互聯(lián)是一家專業(yè)提供長嶺企業(yè)網(wǎng)站建設(shè),專注與做網(wǎng)站、網(wǎng)站制作H5開發(fā)、小程序制作等業(yè)務(wù)。10年已為長嶺眾多企業(yè)、政府機(jī)構(gòu)等服務(wù)。創(chuàng)新互聯(lián)專業(yè)網(wǎng)站設(shè)計(jì)公司優(yōu)惠進(jìn)行中。

事件起因

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

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

我瀏覽了一下出現(xiàn)該問題的業(yè)務(wù)代碼,大概的使用方式是父進(jìn)程調(diào)用 os/exec 下的 Command 開子進(jìn)程執(zhí)行 shell 命令。Command 后面會調(diào)用 golang 封裝的 forkExec 來開子進(jìn)程并執(zhí)行命令,forkExec 使用了 ForkLock。

問題分析

ForkLock 的存在是為了避免下面的情況:在有多個(gè) goroutine 同時(shí) fork exec 的情況下, 為了子進(jìn)程只繼承它需要的文件描述符,需要在父進(jìn)程在創(chuàng)建這些文件描述符的時(shí)候加上 O_CLOEXEC 標(biāo)志,這樣在子進(jìn)程中這些描述符是關(guān)閉的,子進(jìn)程按需把自己需要繼承的描述符打開即可。

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

一個(gè)GO語言性能問題的發(fā)現(xiàn)和解決

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

原因猜測

pipe2 是一個(gè)快速系統(tǒng)調(diào)用,因此可能 block 的系統(tǒng)調(diào)用是 clone 和 exec, 加上在 go1.10 上這個(gè)問題沒有重現(xiàn),對比 go1.8 代碼和 go1.9 在 forkAndExecInChild 函數(shù)上的差異:

go1.8

一個(gè)GO語言性能問題的發(fā)現(xiàn)和解決

go1.9

一個(gè)GO語言性能問題的發(fā)現(xiàn)和解決

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

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

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

Go1.9 加上這兩個(gè)參數(shù)是為了讓子進(jìn)程和父進(jìn)程共享內(nèi)存,相當(dāng)于調(diào)用 vfork, 不需要拷貝頁表項(xiàng), 加快創(chuàng)建速度,從測試效果看,穩(wěn)定在幾十微妙。

一個(gè)GO語言性能問題的發(fā)現(xiàn)和解決

所以一個(gè)合理的猜測是,在低于 go1.9 版寫的程序中,當(dāng)程序內(nèi)存占用足夠大,而且創(chuàng)建進(jìn)程頻率足夠頻繁,會導(dǎo)致 ForkLock 長時(shí)間等待。

實(shí)驗(yàn)論證

一個(gè)GO語言性能問題的發(fā)現(xiàn)和解決

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

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

一個(gè)GO語言性能問題的發(fā)現(xiàn)和解決

一個(gè)GO語言性能問題的發(fā)現(xiàn)和解決

而在 go1.9 及以上版本上并未出現(xiàn)上述情況,這個(gè)結(jié)果我覺得已經(jīng)可以說明問題。升級版本到 go1.9 及以上版本可以解決該問題。

寫在最后

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

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

一個(gè)GO語言性能問題的發(fā)現(xiàn)和解決

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

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

一個(gè)GO語言性能問題的發(fā)現(xiàn)和解決

一個(gè)GO語言性能問題的發(fā)現(xiàn)和解決

更多技術(shù)干貨,請關(guān)注?“云計(jì)算總動(dòng)員”?,我們一起在這里,用云計(jì)算改變未來。


名稱欄目:一個(gè)GO語言性能問題的發(fā)現(xiàn)和解決
文章URL:http://weahome.cn/article/jgdggi.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部