在程式中延遲或者等待一段時間一般可以使用Sleep函式實現,但是因為作業系統執行緒排程的消耗,往往只能做到十幾或者數十毫秒的精度,很難達到微秒級,Golang的time.Sleep也是如此。
Sleep函式一般都會將當前執行緒從CPU讓出,然後等待作業系統的重新排程,這樣可以有效利用CPU資源,但也是Sleep時間精度不高的“罪魁禍首”。因為作業系統執行緒排程並不是實時的,它會先對執行緒排隊,然後在合適的時機才進行真正的CPU切換,我們可以想象還會有佇列優先順序以及插隊的情況存在。
那麼如何才能降低到微秒級呢?本著有現成的就不造輪子的原則,我找遍了各個技術角落也沒有找到,當然很可能有遺漏的地方。不過雖然沒有Golang的實現,但是找到了一些C語言版本的延遲函式。參考它們,我實現了Golang的版本,程式碼如下:
func DelayMicroseconds(us int64) {
var tv syscall.Timeval
_ = syscall.Gettimeofday(&tv) stratTick := int64(tv.Sec)*int64(1000000) + int64(tv.Usec) + us
endTick := int64(0)
for endTick < stratTick {
_ = syscall.Gettimeofday(&tv)
endTick = int64(tv.Sec)*int64(1000000) + int64(tv.Usec)
}
}
你可能會說,這不就是一個死迴圈嗎?!是的,這就是一個死迴圈,也因此它的名字是Delay,而不是Sleep。
這是目前找到的最好的實現方式了,雖然會導致CPU繁忙,但是也只是在短暫的微秒級別,有時候還是能接受的。
天空飄來五個字,那都不是事,是事也就煩一會,一會就沒事。
這裡邊通過系統呼叫獲取了精確到微秒級別的當前時間,然後通過迴圈不斷獲取當前時間,與deadline進行比較,在達到或超出deadline時跳出迴圈,最終實現了指定微秒的時間延遲。
經過實測這個函式在Linux上可以實現微秒級別的延遲,不過雖然Windows上也有Gettimeofday的系統呼叫,但是最低只能做到大概500us左右的延遲,通過看原始碼內部實現機制差別很大。
如果你有更好的方法,歡迎指正。
這段程式碼用在我編寫的一個樹莓派硬體驅動庫中,如有興趣歡迎拍磚:https://github.com/bosima/go-pidriver