Skip to content

sysmon

sysmon เป็นฟังก์ชันธรรมดา แปลตรงตัวว่าตัวตรวจสอบระบบ เมื่อลบส่วนความคิดเห็นออกแล้วจะมีโค้ดประมาณ 200 บรรทัด มันจะถูกกำหนดเธรดแยกต่างหากเพื่อเริ่มต้นในช่วงการนำทางโปรแกรม หลังจากนั้นจะทำงานในพื้นหลังอย่างต่อเนื่องเพื่อตรวจสอบสถานะขณะรันไทม์ของโปรแกรม Go และดำเนินการตอบสนองที่เหมาะสม สำหรับโค้ดส่วนการเริ่มต้นนี้สามารถดูได้ในฟังก์ชัน runtime.main:

go
func main() {
    ...
  mp := getg().m
  mainStarted = true
  systemstack(func() {
        newm(sysmon, nil, -1)
    })
    ...
}

ตัวตรวจสอบระบบเองเป็นเพียงลูป for หนึ่งรอบ ช่วงเวลาระหว่างรอบคือ 20 ไมโครวินาที เมื่อค่าดัชนีการว่างของโปรแกรมเพิ่มขึ้น ช่วงเวลาจะเพิ่มขึ้นสูงสุดเป็น 10 มิลลิวินาที ในแต่ละรอบของลูป มันจะทำสิ่งหลักๆ ต่อไปนี้:

  • ช่วยการกำหนดการคอร์รูทีน ยึดการควบคุมคอร์รูทีนที่ทำงานเป็นเวลานาน
  • ตรวจสอบสถานการณ์หน่วยความจำ และตัดสินว่าจำเป็นต้องทำ garbage collection หรือไม่
  • คืนหน่วยความจำที่ไม่ได้ใช้ให้กับระบบปฏิบัติการ
  • ปิดการเชื่อมต่อที่ไม่ได้ใช้เป็นเวลานานใน network poller

การยึดการควบคุม

ในส่วน นโยบายการกำหนดการ ได้กล่าวไว้ว่า ตัวตรวจสอบระบบเป็นหนึ่งในจุดเริ่มต้นของการยึดการควบคุมแบบอะซิงโครนัส เมื่อคอร์รูทีนทำงานเกิน 10 มิลลิวินาที ตัวตรวจสอบระบบจะบังคับให้เกิดการยึดการควบคุม งานส่วนนี้ทำโดยฟังก์ชัน runtime.retake ด้านล่างคือโค้ดที่ลดทอนแล้ว:

go
func retake(now int64) uint32 {
  n := 0
  lock(&allpLock)
  for i := 0; i < len(allp); i++ {
    pp := allp[i]
    if pp == nil {
      continue
    }
    pd := &pp.sysmontick
    s := pp.status
    sysretake := false
    if s == _Prunning || s == _Psyscall {
      // Preempt G if it's running for too long.
      t := int64(pp.schedtick)
      if int64(pd.schedtick) != t {
        pd.schedtick = uint32(t)
        pd.schedwhen = now
      } else if pd.schedwhen+forcePreemptNS <= now {
        preemptone(pp)
        sysretake = true
      }
    }
  }
  unlock(&allpLock)
  return uint32(n)
}

Garbage Collection

ตัวตรวจสอบระบบจะตรวจสอบสถานการณ์หน่วยความจำอย่างสม่ำเสมอ เพื่อตัดสินว่าจำเป็นต้องทำ garbage collection หรือไม่ เมื่อเวลาผ่านไปเกิน 2 นาทีนับจาก GC ครั้งล่าสุด จะบังคับให้เกิด GC หนึ่งครั้ง

go
if lastgc != 0 && now-lastgc > forcegcperiod && atomic.Load(&forcegc.idle) != 0 {
  lock(&forcegc.lock)
  forcegc.idle = 1
  var list gList
  list.push(forcegc.g)
  injectglist(&list)
  unlock(&forcegc.lock)
}

การคืนหน่วยความจำ

ตัวตรวจสอบระบบจะคืนหน่วยความจำที่ไม่ได้ใช้ให้กับระบบปฏิบัติการ งานส่วนนี้ทำโดยฟังก์ชัน runtime.scavenge

go
scavengeThreshold := scavengeRatio * float64(memstats.heap_inuse)
if scavengeThreshold > float64(scavengeMinPagesToRetain) {
  scavenge(uintptr(scavengeThreshold), false)
}

Network Poller

ตัวตรวจสอบระบบจะปิดการเชื่อมต่อที่ไม่ได้ใช้เป็นเวลานานใน network poller เพื่อหลีกเลี่ยงการสูญเสียทรัพยากร

go
if netpollinited() && sched.lastpoll != 0 {
  if now-sched.lastpoll > 10*1e9 {
    closeIdleNetpollConns()
  }
}

ช่วงเวลาลูป

ช่วงเวลาลูปของตัวตรวจสอบระบบจะปรับเปลี่ยนแบบไดนามิกตามระดับความว่างของโปรแกรม อย่างน้อยคือ 20 ไมโครวินาที อย่างมากคือ 10 มิลลิวินาที

go
delay := uint32(20) * uint32(sched.idle)
if delay > 10*1000 {
  delay = 10 * 1000
}
usleep(delay)

Golang by www.golangdev.cn edit