Go / Goroutine үндэс

Goroutine үндэс

Go хэлийг бусад хэлнүүдээс тодотгон ялгаруулдаг хамгийн гол онцлог бол concurrency — нэгэн зэрэг олон ажлыг гүйцэтгэх чадвар. Goroutine бол Go-ийн concurrency-ийн суурь хэрэгсэл юм.

Goroutine гэж юу вэ?

Goroutine нь Go runtime-ийн удирдан зохицуулдаг, маш хөнгөн гүйцэтгэлийн нэгж. Thread-тэй адилхан боловч хамаагүй хямд:

  • Нэг OS thread — хэдэн МБ санах ой
  • Нэг goroutine — ердөө ~2 КБ санах ой

Нэг программ дотор мянга, тэр ч байтугай сая goroutine ажиллуулж болно.

go түлхүүр үг

Функцийн өмнө go бичихэд тухайн функц шинэ goroutine дотор ажиллана:

go
package main

import (
    "fmt"
    "time"
)

func sayHello(name string) {
    fmt.Printf("Сайн уу, %s!\n", name)
}

func main() {
    go sayHello("Бат")     // Goroutine дотор
    go sayHello("Дорж")    // Горутин дотор
    sayHello("Болд")       // Үндсэн goroutine дотор

    // Goroutine-үүд дуусахыг хүлээх (түр шийдэл)
    time.Sleep(100 * time.Millisecond)
}

go sayHello("Бат") нь функцийг дуусахыг хүлээхгүйгээр шууд дараагийн мөрт шилждэг.

Goroutine-ийн дараалал тодорхойгүй

Goroutine-үүдийн гүйцэтгэлийн дараалал нь урьдчилан тодорхойгүй. Дараах кодыг хэд хэдэн удаа ажиллуулахад өөр өөр дараалалтай хэвлэгдэж болно:

go
package main

import (
    "fmt"
    "time"
)

func printNumbers(id int) {
    for i := 1; i <= 3; i++ {
        fmt.Printf("Goroutine %d: %d\n", id, i)
    }
}

func main() {
    go printNumbers(1)
    go printNumbers(2)
    go printNumbers(3)

    time.Sleep(200 * time.Millisecond)
    fmt.Println("Дууслаа")
}

Гаралт дараалал өөрчлөгдөж болно — энэ нь бүрэн хэвийн зүйл. Runtime нь goroutine-үүдийг тохиромжтой гэж үзсэн дарааллаар гүйцэтгэнэ.

time.Sleep — муу шийдэл

Дээрх жишээнүүдэд time.Sleep ашиглаж горутинүүдийг хүлээсэн нь зохих шийдэл биш. Ямар хугацаа хүлээх нь тодорхойгүй.

Зөв шийдэл нь sync.WaitGroup (дараагийн хичээлд) эсвэл channel (27-р хичээлд) юм. Гэхдээ горутинийг ойлгохын тулд эхлээд time.Sleep ашиглан туршиж болно.

Горутин дотор анонимная функц

Функц тодорхойлоод шууд горутин болгож болно:

go
package main

import (
    "fmt"
    "time"
)

func main() {
    for i := 1; i <= 5; i++ {
        i := i // Маш чухал! Утгыг хуулах
        go func() {
            fmt.Printf("Ажил %d дууслаа\n", i)
        }()
    }

    time.Sleep(200 * time.Millisecond)
}

i := i гэж давтан бичих нь чухал. Ингэхгүй бол бүх горутин нэг i хувьсагчийг хуваалцаж, loop дуусахаас өмнө утга өөрчлөгддөг буруу үр дүн гарна.

Goroutine ба main функц

main функц дуусахад бүх goroutine-үүд зогсдог — дуусаагүй ч байсан:

go
package main

import "fmt"

func longTask() {
    for i := 0; i < 1000; i++ {
        fmt.Println("Ажиллаж байна...", i)
    }
}

func main() {
    go longTask()
    fmt.Println("Main дууслаа — програм гарна")
    // longTask ажиллаж байсан ч энд зогсоно
}

Иймээс горутинүүдийг хүлээх механизм заавал хэрэгтэй.

Дараагийн хичээлд:

Горутинүүдийн хооронд өгөгдөл дамжуулах хэрэгсэл болох channel-ийг сурна. Channel нь Go-ийн "горутинүүд харилцдаг гол гүүр" юм.