进程,线程,协程

进程:为了占据内存空间

  1. 操作系统 “程序” 最新小单位
  2. 进程用来占用空间
  3. 如果计算机是一个工厂,进程相当于厂房,占用工厂空间

image.png

线程:占用工厂(CPU)处理能力

  1. 每个进程可以有多个线程
  2. 线程使用系统分配给进程的内存,线程之间共享内存

image.png

线程原理

  1. 线程用来占用CPU时间
  2. 线程的调度需要由系统进行,开销较大
  3. 线程相当于工厂的生产线,占用工人的工时
  4. 线程里跑的程序就是生产流程

    线程的问题

  5. 线程本身占用资源大

  6. 线程的操作开销大
  7. 线程切换开销大

    协程

image.png
image.png

总结

  1. 进程用徕分配内存空间
  2. 线程用来分配CPU时间
  3. 协程用来切换不同的线程
  4. 协程的本质是一段包含运行状态的程序

    协程的本质

    协程底层结构

    1. type g struct {
    2. // Stack parameters.
    3. // stack describes the actual stack memory: [stack.lo, stack.hi).
    4. // stackguard0 is the stack pointer compared in the Go stack growth prologue.
    5. // It is stack.lo+StackGuard normally, but can be StackPreempt to trigger a preemption.
    6. // stackguard1 is the stack pointer compared in the C stack growth prologue.
    7. // It is stack.lo+StackGuard on g0 and gsignal stacks.
    8. // It is ~0 on other goroutine stacks, to trigger a call to morestackc (and crash).
    9. stack stack // offset known to runtime/cgo 协程栈
    10. stackguard0 uintptr // offset known to liblink
    11. stackguard1 uintptr // offset known to liblink
    12. _panic *_panic // innermost panic - offset known to liblink
    13. _defer *_defer // innermost defer
    14. m *m // current m; offset known to arm liblink
    15. sched gobuf //记录栈指针,和运行到的行数
    16. syscallsp uintptr // if status==Gsyscall, syscallsp = sched.sp to use during gc
    17. syscallpc uintptr // if status==Gsyscall, syscallpc = sched.pc to use during gc
    18. stktopsp uintptr // expected sp at top of stack, to check in traceback
    19. param unsafe.Pointer // passed parameter on wakeup
    20. atomicstatus uint32 //协程状态
    21. stackLock uint32 // sigprof/scang lock; TODO: fold in to atomicstatus
    22. goid int64 //协程的id
    23. schedlink guintptr
    24. waitsince int64 // approx time when the g become blocked
    25. waitreason waitReason // if status==Gwaiting
    26. preempt bool // preemption signal, duplicates stackguard0 = stackpreempt
    27. preemptStop bool // transition to _Gpreempted on preemption; otherwise, just deschedule
    28. preemptShrink bool // shrink stack at synchronous safe point
    29. // asyncSafePoint is set if g is stopped at an asynchronous
    30. // safe point. This means there are frames on the stack
    31. // without precise pointer information.
    32. asyncSafePoint bool
    33. paniconfault bool // panic (instead of crash) on unexpected fault address
    34. gcscandone bool // g has scanned stack; protected by _Gscan bit in status
    35. throwsplit bool // must not split stack
    36. // activeStackChans indicates that there are unlocked channels
    37. // pointing into this goroutine's stack. If true, stack
    38. // copying needs to acquire channel locks to protect these
    39. // areas of the stack.
    40. activeStackChans bool
    41. // parkingOnChan indicates that the goroutine is about to
    42. // park on a chansend or chanrecv. Used to signal an unsafe point
    43. // for stack shrinking. It's a boolean value, but is updated atomically.
    44. parkingOnChan uint8
    45. raceignore int8 // ignore race detection events
    46. sysblocktraced bool // StartTrace has emitted EvGoInSyscall about this goroutine
    47. sysexitticks int64 // cputicks when syscall has returned (for tracing)
    48. traceseq uint64 // trace event sequencer
    49. tracelastp puintptr // last P emitted an event for this goroutine
    50. lockedm muintptr
    51. sig uint32
    52. writebuf []byte
    53. sigcode0 uintptr
    54. sigcode1 uintptr
    55. sigpc uintptr
    56. gopc uintptr // pc of go statement that created this goroutine
    57. ancestors *[]ancestorInfo // ancestor information goroutine(s) that created this goroutine (only used if debug.tracebackancestors)
    58. startpc uintptr // pc of goroutine function
    59. racectx uintptr
    60. waiting *sudog // sudog structures this g is waiting on (that have a valid elem ptr); in lock order
    61. cgoCtxt []uintptr // cgo traceback context
    62. labels unsafe.Pointer // profiler labels
    63. timer *timer // cached timer for time.Sleep
    64. selectDone uint32 // are we participating in a select and did someone win the race?
    65. // Per-G GC state
    66. // gcAssistBytes is this G's GC assist credit in terms of
    67. // bytes allocated. If this is positive, then the G has credit
    68. // to allocate gcAssistBytes bytes without assisting. If this
    69. // is negative, then the G must correct this by performing
    70. // scan work. We track this in bytes to make it fast to update
    71. // and check for debt in the malloc hot path. The assist ratio
    72. // determines how this corresponds to scan work debt.
    73. gcAssistBytes int64
    74. }

    image.png

  5. runtime中,协程本质是一个g结构体

  6. stack:堆栈地址
  7. gobuf:目前协程运行状态(sp,协程栈。pc 程序运行行数)
  8. atomicstatus:协程状态

    golang将系统线程抽象表达出

    1. type m struct {
    2. g0 *g // 初始协程,启动其他协程的协程
    3. morebuf gobuf // gobuf arg to morestack
    4. divmod uint32 // div/mod denominator for arm - known to liblink
    5. // Fields not known to debuggers.
    6. procid uint64 // for debuggers, but offset not hard-coded
    7. gsignal *g // signal-handling g
    8. goSigStack gsignalStack // Go-allocated signal handling stack
    9. sigmask sigset // storage for saved signal mask
    10. tls [6]uintptr // thread-local storage (for x86 extern register)
    11. mstartfn func()
    12. curg *g // current running goroutine 正在运行的协程结构体g
    13. caughtsig guintptr // goroutine running during fatal signal
    14. p puintptr // attached p for executing go code (nil if not executing go code)
    15. nextp puintptr
    16. oldp puintptr // the p that was attached before executing a syscall
    17. id int64 线程id
    18. mallocing int32
    19. throwing int32
    20. preemptoff string // if != "", keep curg running on this m
    21. locks int32
    22. dying int32
    23. profilehz int32
    24. spinning bool // m is out of work and is actively looking for work
    25. blocked bool // m is blocked on a note
    26. newSigstack bool // minit on C thread called sigaltstack
    27. printlock int8
    28. incgo bool // m is executing a cgo call
    29. freeWait uint32 // if == 0, safe to free g0 and delete m (atomic)
    30. fastrand [2]uint32
    31. needextram bool
    32. traceback uint8
    33. ncgocall uint64 // number of cgo calls in total
    34. ncgo int32 // number of cgo calls currently in progress
    35. cgoCallersUse uint32 // if non-zero, cgoCallers in use temporarily
    36. cgoCallers *cgoCallers // cgo traceback if crashing in cgo call
    37. doesPark bool // non-P running threads: sysmon and newmHandoff never use .park
    38. park note
    39. alllink *m // on allm
    40. schedlink muintptr
    41. lockedg guintptr
    42. createstack [32]uintptr // stack that created this thread.
    43. lockedExt uint32 // tracking for external LockOSThread
    44. lockedInt uint32 // tracking for internal lockOSThread
    45. nextwaitm muintptr // next m waiting for lock
    46. waitunlockf func(*g, unsafe.Pointer) bool
    47. waitlock unsafe.Pointer
    48. waittraceev byte
    49. waittraceskip int
    50. startingtrace bool
    51. syscalltick uint32
    52. freelink *m // on sched.freem
    53. // mFixup is used to synchronize OS related m state
    54. // (credentials etc) use mutex to access. To avoid deadlocks
    55. // an atomic.Load() of used being zero in mDoFixupFn()
    56. // guarantees fn is nil.
    57. mFixup struct {
    58. lock mutex
    59. used uint32
    60. fn func(bool) bool
    61. }
    62. // these are here because they are too large to be on the stack
    63. // of low-level NOSPLIT functions.
    64. libcall libcall
    65. libcallpc uintptr // for cpu profiler
    66. libcallsp uintptr
    67. libcallg guintptr
    68. syscall libcall // stores syscall parameters on windows
    69. vdsoSP uintptr // SP for traceback while in VDSO call (0 if not in call)
    70. vdsoPC uintptr // PC for traceback while in VDSO call
    71. // preemptGen counts the number of completed preemption
    72. // signals. This is used to detect when a preemption is
    73. // requested, but fails. Accessed atomically.
    74. preemptGen uint32
    75. // Whether this is a pending preemption signal on this M.
    76. // Accessed atomically.
    77. signalPending uint32
    78. dlogPerM
    79. mOS
    80. // Up to 10 locks held by this m, maintained by the lock ranking code.
    81. locksHeldLen int
    82. locksHeld [10]heldLockInfo
    83. }
  9. 将系统线程抽象为结构体m

  10. g0:g0协程,操作调度器
  11. curg:current g,目前运行的g
  12. mOS:操作系统线程信息

    协程如何执行的

    在go1.0版本,启动了多线程循环抢占式调用全局协程队列中的协程,要先通过全局协程队列的锁,才能调用协程
    image.png
    image.png

  13. 操作系统不知道协程goroutine的存在

  14. 操作系统线程执行一个调度循环,顺序执行goroutine
  15. 调度循环非常像线程池

    总结

  16. 协程本质是一个g结构体

  17. g结构体记录了sp(协程栈),pc(程序运行行数)
  18. 最简情况下,线程执行标准调度循环,执行协程

G-M-P调度模型

解决的问题

多线程并发时,会抢夺全局协程队里的锁,造成锁的冲突和锁的等待

p(本地队列)的结构体

  1. type p struct {
  2. id int32
  3. status uint32 // one of pidle/prunning/...
  4. link puintptr
  5. schedtick uint32 // incremented on every scheduler call
  6. syscalltick uint32 // incremented on every system call
  7. sysmontick sysmontick // last tick observed by sysmon
  8. m muintptr // 指向的服务的m
  9. mcache *mcache
  10. pcache pageCache
  11. raceprocctx uintptr
  12. deferpool [5][]*_defer // pool of available defer structs of different sizes (see panic.go)
  13. deferpoolbuf [5][32]*_defer
  14. // Cache of goroutine ids, amortizes accesses to runtime·sched.goidgen.
  15. goidcache uint64
  16. goidcacheend uint64
  17. // Queue of runnable goroutines. Accessed without lock.
  18. //本地队列,可以无锁访问
  19. runqhead uint32
  20. runqtail uint32
  21. runq [256]guintptr
  22. // runnext, if non-nil, is a runnable G that was ready'd by
  23. // the current G and should be run next instead of what's in
  24. // runq if there's time remaining in the running G's time
  25. // slice. It will inherit the time left in the current time
  26. // slice. If a set of goroutines is locked in a
  27. // communicate-and-wait pattern, this schedules that set as a
  28. // unit and eliminates the (potentially large) scheduling
  29. // latency that otherwise arises from adding the ready'd
  30. // goroutines to the end of the run queue.
  31. runnext guintptr //下一个可用协程的指针
  32. // Available G's (status == Gdead)
  33. gFree struct {
  34. gList
  35. n int32
  36. }
  37. sudogcache []*sudog
  38. sudogbuf [128]*sudog
  39. // Cache of mspan objects from the heap.
  40. mspancache struct {
  41. // We need an explicit length here because this field is used
  42. // in allocation codepaths where write barriers are not allowed,
  43. // and eliminating the write barrier/keeping it eliminated from
  44. // slice updates is tricky, moreso than just managing the length
  45. // ourselves.
  46. len int
  47. buf [128]*mspan
  48. }
  49. tracebuf traceBufPtr
  50. // traceSweep indicates the sweep events should be traced.
  51. // This is used to defer the sweep start event until a span
  52. // has actually been swept.
  53. traceSweep bool
  54. // traceSwept and traceReclaimed track the number of bytes
  55. // swept and reclaimed by sweeping in the current sweep loop.
  56. traceSwept, traceReclaimed uintptr
  57. palloc persistentAlloc // per-P to avoid mutex
  58. _ uint32 // Alignment for atomic fields below
  59. // The when field of the first entry on the timer heap.
  60. // This is updated using atomic functions.
  61. // This is 0 if the timer heap is empty.
  62. timer0When uint64
  63. // The earliest known nextwhen field of a timer with
  64. // timerModifiedEarlier status. Because the timer may have been
  65. // modified again, there need not be any timer with this value.
  66. // This is updated using atomic functions.
  67. // This is 0 if the value is unknown.
  68. timerModifiedEarliest uint64
  69. // Per-P GC state
  70. gcAssistTime int64 // Nanoseconds in assistAlloc
  71. gcFractionalMarkTime int64 // Nanoseconds in fractional mark worker (atomic)
  72. // gcMarkWorkerMode is the mode for the next mark worker to run in.
  73. // That is, this is used to communicate with the worker goroutine
  74. // selected for immediate execution by
  75. // gcController.findRunnableGCWorker. When scheduling other goroutines,
  76. // this field must be set to gcMarkWorkerNotWorker.
  77. gcMarkWorkerMode gcMarkWorkerMode
  78. // gcMarkWorkerStartTime is the nanotime() at which the most recent
  79. // mark worker started.
  80. gcMarkWorkerStartTime int64
  81. // gcw is this P's GC work buffer cache. The work buffer is
  82. // filled by write barriers, drained by mutator assists, and
  83. // disposed on certain GC state transitions.
  84. gcw gcWork
  85. // wbBuf is this P's GC write barrier buffer.
  86. //
  87. // TODO: Consider caching this in the running G.
  88. wbBuf wbBuf
  89. runSafePointFn uint32 // if 1, run sched.safePointFn at next safe point
  90. // statsSeq is a counter indicating whether this P is currently
  91. // writing any stats. Its value is even when not, odd when it is.
  92. statsSeq uint32
  93. // Lock for timers. We normally access the timers while running
  94. // on this P, but the scheduler can also do it from a different P.
  95. timersLock mutex
  96. // Actions to take at some time. This is used to implement the
  97. // standard library's time package.
  98. // Must hold timersLock to access.
  99. timers []*timer
  100. // Number of timers in P's heap.
  101. // Modified using atomic instructions.
  102. numTimers uint32
  103. // Number of timerModifiedEarlier timers on P's heap.
  104. // This should only be modified while holding timersLock,
  105. // or while the timer status is in a transient state
  106. // such as timerModifying.
  107. adjustTimers uint32
  108. // Number of timerDeleted timers in P's heap.
  109. // Modified using atomic instructions.
  110. deletedTimers uint32
  111. // Race context used while executing timer functions.
  112. timerRaceCtx uintptr
  113. // preempt is set to indicate that this P should be enter the
  114. // scheduler ASAP (regardless of what G is running on it).
  115. preempt bool
  116. pad cpu.CacheLinePad
  117. }
  • p结构体中的m:指向线程
  • 队列的 runqhead:头部队列
  • runqtail:尾队列
  • runq:最大长度为256协程队列
  • runnext:队列中下一个运行的协程

image.png

g-m-p的执行细节

m每次获取协程执行的时候,会先在本地队列p中去调用g,调用完之后释放,然后开始调用下一个g,当本地队列p中的g消耗完之后,会去获取全局队列的锁,然后拿去一部分g,放到本地队列p中。如果全局队列的g没了,就会去其他p中获取g。
image.png

源码分析本地队列无G后

  1. // One round of scheduler: find a runnable goroutine and execute it.
  2. // Never returns.
  3. // 线程循环的方法
  4. func schedule() {
  5. _g_ := getg()
  6. .....
  7. var gp *g
  8. .....
  9. // 先到本地队列获取协程
  10. if gp == nil {
  11. gp, inheritTime = runqget(_g_.m.p.ptr())
  12. // We can see gp != nil here even if the M is spinning,
  13. // if checkTimers added a local goroutine via goready.
  14. }
  15. // 上面的本地队列没有g,就会去全局队列或者其他本地队列获取g
  16. if gp == nil {
  17. gp, inheritTime = findrunnable() // blocks until work is available
  18. }
  19. ......
  20. }

p的作用

  1. M与G之间的中间(送料器)
  2. p持有一些G,使得每次获取G的时候不用从全局找
  3. 大大减少并发冲突的情况

    新建的协程

  4. 随机寻找一个p

  5. 将协程房屋p的runnext,go的调度户优先执行新的协程
  6. 若本地队列满了,将会放入全局队列

    实现协程并发

    解决:协程顺序执行,无法并发的问题

    协程顺序执行容易产生饥饿问题

    当下只有两个M,其中正在执行的G需要较长时间,在本地队列P的G都无法执行,无法进行本地队列循环。
    image.png

    解决本地队列协程饥饿问题

  7. 将正在执行的协程存起来,并停止

  8. 将停止的协程放到runnablue队列的尾部,
  9. 协程机型循环,重新运行新的G

image.png
image.png
该方法:可能会造成全局队列的协程饥饿
image.png

  1. 解决全局队列协程饥饿

go解决饥饿问题的方法:每执行61次协程循环,就会从全局队列中拿一个协程放到本地队列
image.png

切换时机(切换协程的方案)

主动挂起

调用sleep,channel时会主动挂起
image.png

系统条用完成时

如:检查网络状态
image.png

总结

  1. 如果协程顺序执行,会有饥饿问题
  2. 协程执行超时,协程执行中间,将协程挂起,执行其他协程
  3. 完成系统调用时挂起,也可以主动挂起
  4. 防止全局队列饥饿,本地队列随机抽取去全局队列

    抢占式调度

    协作抢占

    解决的问题

  5. 永远不主动挂起

  6. 永远都不系统调用

    抢占

  7. 在go中,方法do1调用方法do2时,总会被系统插入一个方法 runtime.morestack

image.png

  1. 基于morestack方法,进行标记抢占

image.png
image.png

  1. 如果协程循环中的业务方法,执行了morestack方法,会检查协程是不是标记了抢占(系统监控器标记的),标记后就会出现调用schedule(协程循环重新开始)

image.png


  1. 基于信号抢占

    解决的问题

  2. 永远不主动挂起

  3. 永远都不系统调用
  4. 业务执行中无法标记抢占

    抢占

    image.png
    先注册一个信号处理函数,当GC回收时,发现超时协程会触发函数,向目标线程发送信号,然后触发线程循环调度
    image.png
    image.png

总结

  1. 基于系统调用和主动挂起,协程可能无法调度
  2. 基于协作的抢占式调度:业务主动调用morestack()
  3. 基于信号的抢占式调度:强制线程调用doSigPreempt()

    协程太多

    造成的问题

  4. 文件打开数太大

  5. 超过内存限制
  6. 调度开销过大

    处理协程太多的方案

  7. 优化业务逻辑

  8. 利用channel的缓冲
    1. 利用channel的缓冲区
    2. 启动协程前向channel发送一个空结构体
    3. 协程结束取出一个结构体
    4. 适用于构建一大批系统的协程 ```go func do(i int,ch chan struct{}) { fmt.Println(i) time.Sleep(time.Second) <-ch }

func main() { ch := make(chan struct{},3000) for i := 0; i < math.MaxInt32; i++ { ch<- struct{}{} go do(i,ch) } time.Sleep(time.Hour) } ```

  1. 协程池 (tunny)
    1. 预创建一定数量协程
    2. 将任务送入协程池队列
    3. 协程池不断取出可用协程,执行任务

image.png

  1. 调度系统资源