3.2 گوروتین (goroutine)

3.2 گوروتین (goroutine)

گوروتین ها را می توان به عنوان یک thread سبک در نظر گرفت که بصورت مستقل می توانند همزمان با سایر گوروتین های دیگر کارها را انجام دهند. و همچنین گوروتین ها می توانند به واسطه کانال داده ها را بین هم به اشتراک گذاشته و منتقل کنند.

حداکثر اندازه stack یک گوروتین در زبان گو ۱ گیگابایت می باشد.

 1var maxstacksize uintptr = 1 << 20 // enough until runtime.main sets it for real
 2
 3	if newsize > maxstacksize || newsize > maxstackceiling {
 4		if maxstacksize < maxstackceiling {
 5			print("runtime: goroutine stack exceeds ", maxstacksize, "-byte limit\n")
 6		} else {
 7			print("runtime: goroutine stack exceeds ", maxstackceiling, "-byte limit\n")
 8		}
 9		print("runtime: sp=", hex(sp), " stack=[", hex(gp.stack.lo), ", ", hex(gp.stack.hi), "]\n")
10		throw("stack overflow")
11	}

در زیر با استفاده از کلمه کلیدی go یک نمونه گوروتین ایجاد کردیم و توجه کنید شما فقط توابع را می توانید به صورت همزمان اجرا کنید.

1go functionName(parameters)

به عنوان مثال :

 1package main
 2
 3import (
 4    "fmt"
 5    "time"
 6)
 7
 8func main() {
 9    go start()
10    fmt.Println("Started")
11    time.Sleep(1 * time.Second)
12    fmt.Println("Finished")
13}
14
15func start() {
16    fmt.Println("In Goroutine")
17}
1$ go run main.go
2Started
3In Goroutine
4Finished

در کد فوق ما تابع ()start را توسط کلمه کلیدی go داخل گوروتین قرار دادیم و این تابع بصورت مستقل از تابع main اجرا شد. اما این وسط یک نکته ای وجود دارد. همانطور که گفتیم تابع اصلی جهت اجرا برنامه های زبان گو تابع main می باشد و اگر شما تابعی را توسط گوروتین از main جدا کنید ممکن است فرآیندهای داخل تابع main زود اتمام شود و شما خروجی تابعی که داخل گوروتین گذاشتید را نبینید.

ما در کد بالا با استفاده از تابع Sleep پکیج time یک وقفه ۱ ثانیه گذاشتیم و این وقفه باعث شد تا عملیات داخل تابع ()start تمام شود و خروجی نمایش داده شود.

خب حالا بزارید مثال فوق را بدون وقفه تست کنیم :

 1package main
 2import (
 3    "fmt"
 4)
 5func main() {
 6    go start()
 7    fmt.Println("Started")
 8    fmt.Println("Finished")
 9}
10func start() {
11    fmt.Println("In Goroutine")
12}
1$ go run main.go
2Started
3Finished

در خروجی بالا هرگز پیغام داخل تابع ()start چاپ نمی شود.

1In Goroutine

علت اصلی این اتفاق این است که تابع main خودش داخل یک گوروتین اجرا می شود و زمانیکه شما یک تابع دیگری را داخل گوروتین قرار می دهید تا لحظه ای که تابع برای اجرا برنامه ریزی شود برنامه اتمام می شود.

3.2.1 گوروتین تابع main #

تابع main را وقتی می توانید ایجاد کنید که نام پکیج شما main و گوروتین اصلی شما main باشد. همه گوروتین ها از تابع main شروع می شوند و گوروتین ها بطور همزمان باز می توانند سایر گوروتین ها را اجرا کنند.

زمانیکه شما تابع main را فراخوانی می کنید بخش اصلی و شروع برنامه شما است. و اگر تابع main شما به هر دلیلی متوقف شود یا اتمام شود سایر گوروتین ها از بین می روند.

گوروتین ها چیزی به نام parent یا child ندارند. زمانیکه شما یک گوروتین را اجرا می کنید این گوروتین در کنار سایر گوروتین ها اجرا می شود و کارش را انجام می دهد. زمانی کار یک گوروتین تمام می شود که تابع بازگشت (return) داشته باشد.

بزارید یک مثال بزنیم تا ببینید چیزی به نام parent یا child برای گوروتین نداریم :

 1package main
 2
 3import (
 4    "fmt"
 5    "time"
 6)
 7
 8func main() {
 9    go start()
10    fmt.Println("Started")
11    time.Sleep(1 * time.Second)
12    fmt.Println("Finished")
13}
14
15func start() {
16    go start2()
17    fmt.Println("In Goroutine")
18}
19func start2() {
20    fmt.Println("In Goroutine2")
21}
1$ go run main.go
2Started
3In Goroutine
4In Goroutine2
5Finished

در کد بالا داخل تابع main ما تابع start را با گوروتین اجرا کردیم و داخل تابع start تابع start2 را با گوروتین اجرا کردیم. این ۲ تابع start و start2 در کنار هم اجرا می شود و در نهایت کارشان اتمام می شود و هیچ کدام منتظر دیگری نخواهد بود.

3.2.2 ایجاد گوروتین چندتایی #

شما می توانید n تا گوروتین بطور همزمان در کنار هم اجرا کنید, در زیر یک مثال زدیم ببینید :

 1package main
 2
 3import (
 4    "fmt"
 5    "time"
 6)
 7
 8func execute(id int) {
 9    fmt.Printf("id: %d\n", id)
10}
11
12func main() {
13    fmt.Println("Started")
14    for i := 0; i < 10; i++ {
15        go execute(i)
16    }
17    time.Sleep(time.Second * 2)
18    fmt.Println("Finished")
19}
 1$ go run main.go
 2Started
 3id: 4
 4id: 9
 5id: 1
 6id: 0
 7id: 8
 8id: 2
 9id: 6
10id: 3
11id: 7
12id: 5
13Finished

در کد فوق ما یک حلقه قرار دادیم از i برابر ۰ تا ۱۰ که داخلش تابع execute را ۱۰ بار اجرا می کند. و هربار اجرا می شود خروجی های مختلفی دارد و ترتیبی درست نخواهید علت این اتفاق این است بطور همزمان اجرا می شوند در کنار هم و هرکدام از گوروتین ها زودتر کارش تمام شود خروجی را نمایش می دهد. به همین دلیل ترتیب درستی نخواهد داشت.

3.2.3 زمانبندی گوروتین ها #

زمانیکه یک برنامه گو اجرا می شود. go runtime رشته های (threads) سیستم عامل را راه اندازی می کند که معادل تعداد CPU های logical قابل استفاده برای فرآیند فعلی است. هر یک از logical CPU ها یک هسته مجازی دارد.

1virtual_cores = x*number_of_physical_cores

در کد بالا x برابر است با تعداد thread ها به ازای هر هسته از CPU

در گو ما یک تابع به نام NumCPU داخل پکیج runtime داریم که می توانید تعداد logical Proccessors موجود برای برنامه گو را ببینید.

1package main
2import (
3    "fmt"
4    "runtime"
5)
6func main() {
7    fmt.Println(runtime.NumCPU())
8}

روی سیستم من عدد 8 را چاپ کرد یعنی سیستم من ۴ هسته که هر هسته دارای ۲ threads است. که قابل استفاده برای برنامه گو روی سیستم من می باشد.