Если вы видите что-то необычное, просто сообщите мне. Skip to main content

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выполнения Go code.кода

    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:

P initialization

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:ОС потоками:

OS thread creation

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:список:

M and P 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:списка:

M and P pulled from the 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:файла:

Syscall handoffs P

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получить P inиз theсписка idle"холостых" listи andвернуться resumeк the executionвыполнению
  • putПоместить theгорутину goroutineв inобщую theочередь, globalа queueсвязанный andс put the associatedним M backвернуть 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:разблокирован:

Network poller waiting for the resource

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.циклов.