72 / 73

Internals

Ko'pgina dasturlash tillarida oqim yaratish (thread) operatsion tizim (OS thread) orqali amalga oshiriladi. Nega Go tili millionlab goroutine'larni xotirjam va juda kam resurs sarflab ishlata oladi? Javob Go Runtime Scheduler arxitekturasida yotadi!

Bob mundarijasi:


Concurrency va Parallelism farqi

Rob Pike (Go asoschisi):

"Concurrency is about dealing with lots of things at once. Parallelism is about doing lots of things at once." (Concurrency — bu bir vaqtda ko'p narsalar bilan ishlay olish tuzilishi. Parallelism — bu bir vaqtda ko'p narsalarni haqiqatda jismonan bajarish).

  • Bitta yadroli protsessorda dastur concurrent bo'lishi mumkin (vaqt taqsimoti orqali), lekin parallel bo'la olmaydi.
  • Ko'p yadroli protsessorda esa goroutine'lar turli yadrolarda bir vaqtning o'zida parallel bajariladi.

GMP modeli

Go Runtime Scheduler 3 ta asosiy obyektga tayanadi:

  1. G (Goroutine): Bajarilayotgan goroutine. Unda o'zining stack xotirasi, dastur hisoblagichi (PC) va holati saqlanadi.
  2. M (Machine): Operatsion tizimning haqiqiy thread'i (OS thread). Kod aynan shu yerda bajariladi.
  3. P (Processor): Mantiqiy protsessor (resurs). Har bir P o'zining mahalliy navbatiga (Local Run Queue — 256 ta goroutine sig'imli) ega.
kod
       [ Global Run Queue (GRQ) ]
                  │
        ┌─────────┴─────────┐
        ▼                   ▼
    ┌───────┐           ┌───────┐
    │  P 0  │           │  P 1  │  (Mantiqiy protsessorlar)
    └───┬───┘           └───┬───┘
        │ LRQ               │ LRQ
     [G1, G2]            [G3, G4]
        │                   │
    ┌───┴───┐           ┌───┴───┐
    │  M 0  │           │  M 1  │  (OS thread'lar CPU yadrosida)
    └───────┘           └───────┘

Work Stealing algoritmi

Agar P 0 protsessori o'zining navbatidagi barcha goroutine'larni bajarib bo'lsa va bekor qolib ketsa, u nima qiladi?

  1. U boshqa qo'shni protsessor (P 1) ning navbatiga qaraydi;
  2. U yerdagi goroutine'larning yarmini o'g'irlab oladi (Work Stealing) va o'zida bajarishga kirishadi!
  3. Agar hamma bo'sh bo'lsa, umumiy Global Run Queue'dan oladi;
  4. Agar tarmoq kutayotgan goroutine bo'lsa, Network Poller'dan tekshiradi.

Bu algoritm CPU yadrolarining hech qachon bekor turmasligini va yuklama optimal taqsimlanishini kafolatlaydi!


GOMAXPROCS

runtime.GOMAXPROCS(n) — bu dasturda bir vaqtda parallel kod bajara oladigan P (protsessorlar) sonini belgilaydi. Odatiy holatda u kompyuterdagi mantiqiy CPU yadrolari soniga teng bo'ladi (runtime.NumCPU()).


Goroutine Stack xotirasi

Operatsion tizim thread'lari odatda boshlang'ich 1MB dan 8MB gacha qat'iy stack talab qiladi. Shuning uchun 10 000 ta OS thread ochilsa, kamida 10-80 GB xotira kerak bo'ladi.

Goroutine esa atigi 2 KB boshlang'ich stack bilan tug'iladi! Agar funksiya chaqiruvlari chuqurlashib, 2KB yetmay qolsa, Go runtime uni xotirada avtomatik 4KB, 8KB, 16KB qilib kattalashtiradi (va aksincha, bo'shaganda kichraytiradi). Shu tufayli 100 000 ta goroutine atigi ~200MB xotira bilan ishlayveradi!


Topshiriq: GOMAXPROCS va yadrolar sonini tekshirish

Tizimingizdagi CPU yadrolari sonini va joriy GOMAXPROCS qiymatini ekranga chiqaruvchi dastur yozing.

example.go
package main

import (
	"fmt"
	"runtime"
)

func main() {
	// TODO: NumCPU va GOMAXPROCS qiymatlarini ekranga chiqaring
}

Xulosa

Siz Gist of Go: Concurrency kursining barcha 13 ta bobini muvaffaqiyatli yakunladingiz!

Endi siz Go tilida:

  • Goroutine va kanallarning tub mohiyatini;
  • Oqimli konveyerlar (Pipelines) va vaqt boshqaruvini;
  • Kaskadli bekor qilish (context) tizimini;
  • WaitGroup, Mutex, Semaforlar va Atomik amallarni;
  • Poygalardan himoyalangan xavfsiz concurrent dasturlar yozishni;
  • Go runtime scheduler'ining ichki ishlash mexanizmini to'liq bilasiz.

Tabriklaymiz, siz Go Concurrency bo'yicha professional bilimlarga ega bo'ldingiz!