github.com/Cloud-Foundations/Dominator@v0.3.4/lib/cpusharer/api.go (about) 1 /* 2 Package cpusharer implements co-operative CPU sharing between goroutines. 3 4 Package cpusharer may be used by groups of co-operating goroutines to share 5 CPU resources so that blocking operations are fully concurrent but avoiding 6 the thundering herd problem when large numbers of goroutines need the CPU, 7 impacting the responsiveness of other goroutines such as dashboards and 8 health checks. 9 Each goroutine calls the GrabCpu method when it starts and wraps blocking 10 operations with a pair of ReleaseCpu/GrabCpu calls. 11 A typical programming pattern is: 12 cpuSharer := cpusharer.New*CpuSharer() // Pick your sharer of choice. 13 for work := range workChannel { 14 cpuSharer.GoWhenIdle(0, -1, func(work workType) { 15 work.compute() 16 cpuSharer.ReleaseCpu() 17 work.block() 18 cpuSharer.GrabCpu() 19 work.moreCompute() 20 }(work) 21 } 22 */ 23 package cpusharer 24 25 import ( 26 "sync" 27 "time" 28 ) 29 30 // CpuSharer is the interface that wraps the GrabCpu and ReleaseCpu methods. 31 // 32 // GrabCpu will grab a CPU for use. If there are none available (i.e. all CPUs 33 // are in use by other co-operating goroutines) then this will block until a CPU 34 // is available. 35 // 36 // ReleaseCpu will release a CPU so that another co-operating goroutine can grab 37 // a CPU. 38 type CpuSharer interface { 39 GrabCpu() 40 ReleaseCpu() 41 } 42 43 type FifoCpuSharer struct { 44 semaphore chan struct{} 45 mutex sync.Mutex 46 grabTimeout time.Duration 47 lastAcquireEvent time.Time 48 lastIdleEvent time.Time 49 lastYieldEvent time.Time 50 numIdleEvents uint64 51 Statistics Statistics 52 } 53 54 // NewFifoCpuSharer creates a simple FIFO CpuSharer. CPU access is granted in 55 // the order in which they are requested. 56 func NewFifoCpuSharer() *FifoCpuSharer { 57 return newFifoCpuSharer() 58 } 59 60 // GetStatistics will update and return the Statistics. 61 func (s *FifoCpuSharer) GetStatistics() Statistics { 62 return s.getStatistics() 63 } 64 65 // SetGrabTimeout will change the timeout for the GrabCpu method. A negative 66 // value for timeout means no timeout (this is the default). After the timeout a 67 // panic is generated. 68 // A full stack trace is written to os.Stderr. 69 func (s *FifoCpuSharer) SetGrabTimeout(timeout time.Duration) { 70 s.setGrabTimeout(timeout) 71 } 72 73 // Go will start a goroutine and return. The goroutine will grab a CPU using the 74 // GrabCpu method, then will run goFunc. When goFunc returns the CPU is 75 // released. 76 func (s *FifoCpuSharer) Go(goFunc func()) { 77 startGoroutine(s, goFunc) 78 } 79 80 // GoWhenAvailable will grab a CPU using the GrabCpu method and then starts a 81 // goroutine which will run goFunc. When goFunc returns the CPU is released. 82 // Use GoWhenAvailable to limit the addition of more goroutines if the CPUs are 83 // already saturated with work. This can reduce memory consumption spikes. 84 func (s *FifoCpuSharer) GoWhenAvailable(goFunc func()) { 85 startGoroutineWhenAvailable(s, goFunc) 86 } 87 88 // GoWhenIdle is similar to the GoWhenAvailable method except that it will call 89 // GrabIdleCpu to wait for and grab an idle CPU. Compared to GoWhenAvailable, 90 // GoWhenIdle effectively lowers the priority of starting new goroutines below 91 // the priority of the co-operating goroutines. This can be even more effective 92 // in reducing memory consumption spikes. 93 // GoWhenIdle will wait up to timeout (if negative, it will wait forever) for an 94 // idle CPU. If an idle CPU is grabbed before the timeout it will return true, 95 // otherwise it will not start a goroutine and will return false. 96 func (s *FifoCpuSharer) GoWhenIdle(minIdleTime, timeout time.Duration, 97 goFunc func()) bool { 98 return s.goWhenIdle(minIdleTime, timeout, goFunc) 99 } 100 101 // GrabCpu will grab a CPU for use. If there are none available (i.e. all CPUs 102 // are in use by other co-operating goroutines) then this will block until a CPU 103 // is available. Grab requests are fulfilled in the order they are made. 104 func (s *FifoCpuSharer) GrabCpu() { 105 s.grabCpu() 106 } 107 108 // GrabIdleCpu will wait for a CPU to be idle for at least minIdleTime and then 109 // grabs a CPU. If minIdleTime is zero or less then a CPU is grabbed immediately 110 // once one becomes idle. CPUs will never become idle while there are more 111 // goroutines blocked on GrabCpu than there are CPUs. 112 // GrabIdleCpu will wait up to timeout (if negative, it will wait forever) for 113 // an idle CPU. If an idle CPU is grabbed before the timeout it will return 114 // true, otherwise it will return false. 115 func (s *FifoCpuSharer) GrabIdleCpu(minIdleTime, timeout time.Duration) bool { 116 return s.grabIdleCpu(minIdleTime, timeout) 117 } 118 119 // GrabSemaphore will safely grab the provided semaphore, releasing and 120 // re-acquiring the CPU if the semaphore blocks. Use this to avoid deadlocks. 121 func (s *FifoCpuSharer) GrabSemaphore(semaphore chan<- struct{}) { 122 grabSemaphore(s, semaphore) 123 } 124 125 func (s *FifoCpuSharer) ReleaseCpu() { 126 s.releaseCpu() 127 } 128 129 func (s *FifoCpuSharer) Sleep(duration time.Duration) { 130 sleep(s, duration) 131 } 132 133 type Statistics struct { 134 LastAcquireEvent time.Time 135 LastIdleEvent time.Time 136 LastYieldEvent time.Time 137 NumCpuRunning uint 138 NumCpu uint 139 NumIdleEvents uint64 140 }