首頁 > 軟體

Go中使用單調時鐘獲得準確的時間間隔問題

2022-06-02 14:04:32

牆上時鐘與單調時鐘

牆上時鐘

牆上時鐘也稱為牆上時間。大多是1970年1月1日(UTC)以來的秒數和毫秒數。

牆上時間可以和NTP(Network Time Protocal,網路時間協定)同步,但是如果本地時鐘遠遠快於NTP伺服器,則強制重置之後會跳到先前某個時間點。(這裡不是很確定,猜測是如果時間差的不多,則調整石英晶體振盪器的頻率,慢慢一致。如果差很多,則強行一致)

單調時鐘

機器大多有自己的石英晶體振盪器,並將其作為計時器。單調時鐘的絕對值沒有任何意義,根據作業系統和語言的不同,單調時鐘可能在程式開始時設為0、或在計算機啟動後設為0等等。但是通過比較同一臺計算機上兩次單調時鐘的差,可以獲得相對準確的時間間隔。

Time的結構

type Time struct {
    // wall and ext encode the wall time seconds, wall time nanoseconds,
    // and optional monotonic clock reading in nanoseconds.
    //
    // From high to low bit position, wall encodes a 1-bit flag (hasMonotonic),
    // a 33-bit seconds field, and a 30-bit wall time nanoseconds field.
    // The nanoseconds field is in the range [0, 999999999].
    // If the hasMonotonic bit is 0, then the 33-bit field must be zero
    // and the full signed 64-bit wall seconds since Jan 1 year 1 is stored in ext.
    // If the hasMonotonic bit is 1, then the 33-bit field holds a 33-bit
    // unsigned wall seconds since Jan 1 year 1885, and ext holds a
    // signed 64-bit monotonic clock reading, nanoseconds since process start.
    wall uint64
    ext  int64
    ...
 }

wall和ext共同記錄了時間,但是分為兩種情況,一種是沒有記錄單調時鐘(比如是通過字串解析得到的時間),另一種是記錄了單調時鐘(比如通過Now)。

wall的第一位是一個標記位

如果為1,則表示記錄了單調時鐘。則wall的2-34(閉區間)位記錄了從1885-1-1到現在的秒數,最後30位記錄了納秒數。而ext記錄了從程式開始執行到現在經過的單調時鐘數。

如果為0,則表示沒有記錄單調時鐘。則wall的2-34(閉區間)位全部為0(那最後30位是啥?)。而ext記錄了從1-1-1到現在經過的秒數。

Since的實現

這裡比較關鍵的程式碼是第914行的 runtimeNano() - startNano 。 startNano 的含義還是直接上程式碼比較明瞭。

var startNano = 0
 func init(){
     startNano = runtimeNano()
 }

runtimeNano() 是呼叫了組合,獲取了作業系統當前的單調時鐘。前面說過,單調時鐘的絕對值沒有什麼意義。因此這裡將兩個時間相減,得到了從程式開始到現在的單調時鐘。

然後看一下Sub

func (t Time) Sub(u Time) Duration {
    if t.wall&u.wall&hasMonotonic != 0 {
       te := t.ext
       ue := u.ext
       d := Duration(te - ue)
       if d < 0 && te > ue {
          return maxDuration // t - u is positive out of range
       }
       if d > 0 && te < ue {
          return minDuration // t - u is negative out of range
       }
       return d
    }
    d := Duration(t.sec()-u.sec())*Second + Duration(t.nsec()-u.nsec())
    // Check for overflow or underflow.
    switch {
    case u.Add(d).Equal(t):
       return d // d is correct
    case t.Before(u):
       return minDuration // t - u is negative out of range
    default:
       return maxDuration // t - u is positive out of range
    }
 }

這裡我們只需要關注2-13行即可。除去了範圍檢查,這裡的主要邏輯就是兩個Time的ext相減。而ext又都代表了單調時鐘,所以最後返回的是單調時鐘的差值。

小結

在分散式系統中,我們經常需要判斷時間間隔來檢測心跳。而牆上時鐘與NTP的組合可能會帶來時間的前後跳躍與閃爍,所以使用單調時鐘更加安全和保險。

在go語言中,沒有直接呼叫呼叫時鐘的函數。可以通過 time.Now() 獲得帶單調時鐘的 Time 結構體,並通過Since和Until獲得相對準確的時間間隔。

參考資料

到此這篇關於Go中使用單調時鐘獲得準確的時間間隔的文章就介紹到這了,更多相關go時間間隔內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com