گریزی بر گولنگ goroutines / race condition

Go

گو روتینها (شبه مالتی تریدینگ) در زبان جالب go یکی از ویژگیهاییه که اون رو محبوب کرده و برای مقاصد زیادی هم کاربرد داره و استفاده ازش هم خیلی راحته در این خُردمقاله فقط اشاره ای مختصر به روتینها میکنم ولی این مبحث خیلی جذابه و جای بحث زیادی داره که از حجم این مقاله و علم بنده بر نمیاد کامل شرح داده بشه. به صورت کلی هرگاه ما یک ترد(نه به معنای ترد در پردازنده که در زبانهای C++ و … پیاده میشه) ایجاد میکنیم دستوراتی که داخل ترد نوشته شده در یک ترد دیگه اجرا خواهد شد که ممکنه همزمان/موازی با ترد اصلیمون اجرا بشوند یا غیر همزمان اجرا بشوند. ساختار نوشتن go به صورت زیره که در تابع main برنامه اجرا میشه ، ( برای نوشتن برنامه های اجرایی mainرو داریم نه همه جا! )

package main

func main() {
 //Put your code here...
}
اگر عبارت go رو قبل از یک تابع قرار بدیم اون رو در یک ترد جدا اجرا میکنه:
package main

import (
 "fmt"
 "time"
)

func main() {
    fmt.Println("1:Salam Virgool!")
 go func() {
        fmt.Println("2:Hello Virgool!")
    }()
    fmt.Println("3:Hola Virgool!")
 
 //Sleep for prevent closing
    time.Sleep(5000)
}

به جای sleep میتونستیم از fmt.Scanln برای جلوگیری از بسته شدن برنامه استفاده کنیم خروجی های متفاوتی برای کد بالا میتونیم انتظار داشته باشیم:

1:Salam Virgool!
3:Hola Virgool!
2:Hello Virgool!
1:Salam Virgool!
2:Hello Virgool!
3:Hola Virgool!

پس میتونیتم از این نتیجه بگیریم که اجرای تابع برای چاپ عبارت شماره 2 به ترتیب کدی که نوشتیم ممکنه باشه یا با تاخیر و غیر همزمان باشه! روتینها کجا به کار میان؟ خیلی جاها مثل نوشتن یک روتین برای انجام کارهای زمانبری چون دانلود فایل یا اتصال به شبکه یا اجرای الگوریتمهای زمانبر به طور موازی و انواع و اقسام کار با IO

جهنم race conditions چرا جهنم؟ چون باگهایی که به صورت ریس کاندیشن (شرایط مسابقه) پیش میان همیشه یک خطا در روند اجرایی برنامه رو بوجود نمیارن که بتونیم با خوندن چند خط بالا پایین برنامه اونها رو دیباگ کنیم. یعنی یکبار این باگ هست و یکبار نیست ،یا بدتر هر بار یه چیزی نشون بده :( چطور این باگ بوجود میاد؟ به رفتار این تیکه کد دقت کنید:

package main

import (
 "fmt"
)

func main() {
 i := 6

 go func() {
 i = i + 5
    }()
    i *= 3
  fmt.Println("Press Enter:") 
  fmt.Scanln() 
    fmt.Printf("i = %d \n", i)
}

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

(6+5)=11 * 3 = 33 => i = 33
(6*3)=18 * 5= 23 => i = 23
(6*3)=18 => i = 18

حالا چطور اون چیزی که مد نظرمونه در مورد ترتیب روند اجرای دستورات رو اجباری کنیم؟ با استفاده از کانالها Channels ما میتونیم از شرایط و مقادیر یک روتین باخبر بشیم

package main

import (
 "fmt"
)

func main() {
 i := 6

 MyChannel := make(chan bool)
 go func() {
 i = i + 5
        MyChannel <- true
    }()
 //wait for routine
 <-MyChannel
    i *= 3
    fmt.Println("Press Enter:")
    fmt.Scanln()
    fmt.Printf("i = %d \n", i)
}

میایم قبل از اجرای روتین یک کانال به نام MyChannel میسازیم و بعدش منتظر میمونیم که اون تموم بشه و کار رو ترد اصلی انجام بده که نتیجه همیشه 33 خواهد شد چون به ترتیب عبارت اولمون (6+5)=11 * 3 = 33 => i = 33 برنامه اجرا میشه!

ساده بود نه؟ :)