Go: горутины , потоки ОС и управление ЦПУ
CreatingСоздание anпотоков OSОС Threadили orпереключение switchingиз fromодного oneв toдругой anotherможет canстоить beвашей costlyпрограмме forпамять yourи programsпроизводительность. inЦелью termsgo ofявляется memoryполучение andпреимущества performance.от Goядра aimsнастолько, toнасколько getэто advantagesвозможно. asОн muchбыл asсоздан possibleуже fromс theконкурентностью cores.с Itсамого has been designed with concurrency in mind from the beginning.начала.
M, P, G orchestrationоркестрация
ToЧтобы solveрешить thisэту problem,проблему, Go hasимеет itsсвоё ownрасписание schedulerдля toраспределения distributeгорутин goroutinesчерез overразличные theпотоки. threads.Это Thisрасписание schedulerопределяет definesтри threeглавных mainидеи, concepts,как asсказано explainedв inсамом the code itself:коде:
TheГлавные main concepts are:идеи:
-
G -
goroutine.горутины -
M -
workerрабочийthread,потокorилиmachine.машина -
P -
processor,процессор,aресурсresourceдляthat is required to executeвыполнения Gocode.кодаM
mustдолженhaveиметьanсвязанныйassociatedP,PчтобыtoвыполнятьexecuteкодGo code[...].Go.
HereВот isдиаграмма aэтих diagramтрех of this P, M, G model:моделей.
EachКаждая goroutineгорутина (G) runsзапускается onв anпотоке OS threadОС (M) thatкоторая isназначена assignedлогическому to a logical CPUЦПУ (P). Let’sДавайте takeвозьмем aпростой simpleпример, exampleчтобы toпосмотреть see howкак Go managesуправляет them:ими:
func main() {
var wg sync.WaitGroup
wg.Add(2)
go func() {
println(`hello`)
wg.Done()
}()
go func() {
println(`world`)
wg.Done()
}()
wg.Wait()
}
Сначала Go willсоздаст first create the differentразличные P basedс onномерами theлогических numberЦПУ ofприсутствующих logicalв CPUsмашине ofи theсохранит machineих andв storeкачестве themсписка in a list of idle"холостых" P:
Then,Затем, theновая newгорутина goroutineили orгорутины goroutinesготовые readyк toзапуску runбудут will wake aбудить P upдля toназначения distributeна theних workработ. better.В Thisэтом случае P will create anсоздаст M withи theсвяжет associatedих OSс thread:ОС потоками:
However,Однако, likeкак aи P, a M withбез noработы work- —то i.e.есть noне goroutineимеющая waitingработающей toгорутины runожидающей —запуска, returningвозвращается fromиз asyscall
syscall,или orпринудительно evenзавершенная forcedсборщиком toмусора, beпопадет stoppedв by"холостой" the garbage collector, goes to an idle list:список:
DuringВо theвремя bootstrapзагрузки of the program,программы, Go alreadyуже createsсоздает someпотоки OSОС threadи andсвязывает associatedих с M. ForДля ourнашего example,примера, theпервая firstгорутина goroutineкоторая thatвыводит prints"привет" helloбудет willиспользовать useглавную theгорутину, mainв goroutineто whileвремя theкак secondвторая one will get anполучит M andи P fromиз this"холостого" idle list:списка:
NowТеперь weу haveнас theесть bigобщая pictureкартина ofупрвления theгорутинами goroutinesи andпотоками, threadsдавайте management,посмотрим let’sв see in which caseкакомслучае Go wouldстанет use moreиспользовать M thanчаще чем P andи howкак goroutinesгорутины areуправляются managedв inслучае caseсистемых of system calls.вызовов.
SystemСистемные callsвызовы
Go optimizesоптимизирует theсистемные systemвызовы, callsвне —зависимости whateverот itблокировки, isс blockingпомощью orоборачивания notих —во byвремя wrappingисполнения. themЭта upобертка inбудет theавтоматически runtime. This wrapper will automatically dissociate theотделять P fromот the threadтреда M andи allowпозволять anotherдругим threadтредам toзапускать runего. onДавайте it.посмотрим Let’sна takeпример anчтения example with a file reading:файла:
func main() {
buf := make([]byte, 0, 2)
fd, _ := os.Open("number.txt")
fd.Read(buf)
fd.Close()
println(string(buf)) // 42
}
HereВот isрабочий theпроцесс workflowоткрытия when the file is opening:файла:
P0 isв nowданный inмомент theпростаивает, idleи listпотенциально andдоступен. potentiallyЗатем, available.как Then,только onceсистенмый theвызов syscall exits,завершен, Go appliesприменяет theследующий followingнабор rulesправил untilпока oneодно canиз beправил satisfied:не будет удовлетворено:
tryПопытатсяtoзавладетьacquireтемthe exact sameже P,P0вinнашемourслучаеexample,этоandP0,resumeиtheвернутсяexecutionк выполнению.tryПопытатьсяto acquire aполучить Pinизtheспискаidle"холостых"listиandвернутьсяresumeкthe executionвыполнениюputПоместитьtheгорутинуgoroutineвinобщуюtheочередь,globalаqueueсвязанныйandсput the associatedним Mbackвернутьtoобратноtheвidle"холостую"listочередь.
However,Однакой, Go, так же обрабатывает ситуации когда ресурсы еще не готовы, на случай не блокируемых I/O, например http вызовы. Тогда, первый системый вызов, который следуюет представленному выше рабочему процессу, упадет, так как нет готовых ресурсов, заставляет Go alsoиспользовать handlesсетевой theопросник caseи whenостановливает theгорутину. resourceВот is not ready yet in case of non-blocking I/O such as http call. In this case, the first syscall — that follows the previous workflow — will not succeed since the resource is not yet ready, forcing Go to use the network poller and park the goroutine. Here is an example:пример:
func main() {
http.Get(`https://httpstat.us/200`)
}
OnceКак theпервый firstсистемный syscallвызов isотработает doneи andявно explicitlyскажет, saysчто theресуср resourceне isготов, notгорутина yetостановится ready,до theтех goroutineпор willпока parkсетевой untilопросник theне networkскажет, pollerчто notifiesресурс itготов. thatВ theэтом resourceслучае is now ready. In this case, the threadтред M willбудет not be blocked:разблокирован:
TheГорутина goroutineзаново willзапустится run again when theкогда Go schedulerпланировщик looksначнет forискать work.работу. TheПланировщик schedulerзатем willбудет thenпередавать askсетевому theопроснику, networkчто pollerгорутина ifожидает aзапуска goroutineв isслучае waitingуспешного toполучения beинформации runкоторая after successfully getting the information it was waiting for:ожидается:
IfЕсли moreбольше, thanчем oneодна goroutineгорутина isготова, ready,то theдополнительная extraбудет onesотправлена willв goзапускаемую onглобальную theочреедь globalи runnableбудет queueзапущена andпланировщиком will be scheduled later.позже.
RestrictionОграничения inв termрамках ofпотоков OS threadsОС
WhenКогда systemсистемные callsвызовы are used,использутся, Go doesне notограничивает limitколичество theпотоков numberОС, ofкоторые OSмогут threadsбыть thatзаблокированны, canкак beуказано blocked,в as explained in code:коде:
TheGOMAXPROCS переменная ограничивает количество рабочих системных потоков, которые могут быть запущены пользвателем одновременно. Нет ограничений по количеству потоков которые могут быть заблокированны системными вызовами при выполнении от имени Go кода, отсюда, количество GOMAXPROCS variableограниченно. limitsЭта theфункция number of operating system threads that can execute user-level Go code simultaneously. There is no limit to the number of threads that can be blocked in system calls on behalf of Go code; those do not count against theпакета GOMAXPROCS limit.опрашивает Thisи package’sменяет GOMAXPROCS function queries and changes the limit.количество.
HereВот isпример an example of this situation:ситуации:
func main() {
var wg sync.WaitGroup
for i := 0;i < 100 ;i++ {
wg.Add(1)
go func() {
http.Get(`https://httpstat.us/200?sleep=10000`)
wg.Done()
}()
}
wg.Wait()
}
HereВот areколичество theпотоков numberсозданных ofс threadsпомощью createdинструментов from the tracing tools:слежения:
SinceТак как Go optimizesоптимизирован theдля threadиспользования usage,потоков, itон canможет beбыть re-usedмногократно whileиспользован itsв goroutineто isвремя, blocking,как itгорутины explainsзаблокированны. whyЭто thisобъясняет numberпочему doesэто notчисло matchне withсовпадает theс numberчислом of the loop.циклов.