شرایط مسابقه در Go: یک آموزش ساده

سلام من Shrijith Venkatrama ، بنیانگذار Hexmos هستم. در حال حاضر ، من در حال ساختن LiveApi هستم ، ابزاری که تولید اسناد API را از کد شما به طرز مسخره ای آسان می کند.
به تازگی ، من در برنامه GO خود با چند شرایط مسابقه سر و کار داشته ام که بسیاری از گوروس ها به طور همزمان از بین می روند.
در زمینه های عملی – شرایط مسابقه معمولاً هنگامی اتفاق می افتد که چندین گوروتین به طور همزمان به دسترسی و تغییر داده های مشترک ، منجر به نتایج غیرقابل پیش بینی می شوند.
این آموزش شما را از اصول اولیه گرفته تا نمونه های پیچیده تری از شرایط مسابقه در Go سوق می دهد.
ما از مثالهای ساده و قابل اجرا استفاده خواهیم کرد تا نشان دهیم چه اشتباهی رخ می دهد و چگونه می توان این مسائل را مشاهده کرد.
توجه داشته باشید که من عمدتاً روی سناریوهای معمولی عملی متمرکز شده ام که در آن شرایط مسابقه پدیدار می شود – و نه یک پوشش جامع از امکانات.
شرایط مسابقه چیست؟
یک وضعیت مسابقه زمانی اتفاق می افتد که نتیجه یک برنامه به زمان گوروتین ها بستگی داشته باشد.
اگر دو یا چند گوروتین سعی کنید بدون کنترل همان متغیر را بخوانید و بنویسید ، رفتار حشره ای دریافت می کنید.
بیایید با یک مثال اساسی شروع کنیم که در آن وضعیت مسابقه هنوز اتفاق نمی افتد:
package main
import "fmt"
func main() {
counter := 0
counter++
fmt.Println("Counter:", counter)
}
خروجی:
Counter: 1
در اینجا ، هیچ شرایط مسابقه ای وجود ندارد زیرا ما فقط از یک goroutine استفاده می کنیم ( main
عملکرد).
متغیر counter
با خیال راحت افزایش می یابد اما وقتی ما گوروها را اضافه می کنیم ، همه چیز می تواند اشتباه شود.
معرفی goroutines و یک شرایط مسابقه ساده
حال ، بیایید Goroutines را برای افزایش یک متغیر مشترک اضافه کنیم. اینجاست که شرایط مسابقه ظاهر می شود.
package main
import (
"fmt"
"time"
)
func increment(counter *int) {
*counter++
}
func main() {
counter := 0
go increment(&counter) // First goroutine
go increment(&counter) // Second goroutine
time.Sleep(time.Second) // Wait for goroutines to finish
fmt.Println("Counter:", counter)
}
خروجی (ممکن است متفاوت باشد):
Counter: 1
یا Counter: 2
چه اتفاقی می افتد؟
انتظار داریم counter
2 (دو افزایش) ، اما گاهی اوقات 1 است.
چرا؟ Goroutines برای به روزرسانی مسابقه می دهند counter
بشر
ممکن است کسی تغییر دیگری را بازنویسی کند زیرا هیچ هماهنگی وجود ندارد.
این یک شرایط مسابقه است.
این کار را چندین بار اجرا کنید – نتایج متفاوتی را مشاهده خواهید کرد. این غیرقابل پیش بینی بودن یک مسابقه است!
بخش 3: دیدن شرایط مسابقه در عمل
بیایید آن را با goroutines بیشتر مقیاس کنیم تا مشکل واضح تر شود:
package main
import (
"fmt"
"time"
)
func increment(counter *int) {
*counter++
}
func main() {
counter := 0
for i := 0; i < 100; i++ {
go increment(&counter)
}
time.Sleep(time.Second)
fmt.Println("Counter:", counter)
}
خروجی (ممکن است متفاوت باشد):
Counter: 97
یا Counter: 88
یا چیز دیگری زیر 100
ما 100 goroutine را برای افزایش راه اندازی کردیم counter
، بنابراین باید 100 باشد ، درست است؟ اما تقریباً همیشه کمتر است. هر گوروتین می خواند و می نویسد counter
در عین حال ، و برخی به روزرسانی ها از بین می روند. به عنوان مثال:
- goroutine 1 می خواند
counter = 5
، آن را به 6 افزایش می دهد. - goroutine 2 می خواند
counter = 5
(قبل از نوشتن Goroutine 1) ، آن را به 6 افزایش می دهد. - هر دو دوباره 6 را می نویسند ، یک افزایش را از دست می دهند.
این همپوشانی قلب یک وضعیت مسابقه است.
تشخیص شرایط مسابقه با ردیاب مسابقه GO
GO یک ابزار داخلی برای مشاهده شرایط مسابقه دارد. برنامه را با -race
پرچم 🙂 پرچم 🙂
go run -race yourfile.go
بیایید از آن در آخرین مثال ما استفاده کنیم:
package main
import (
"fmt"
"time"
)
func increment(counter *int) {
*counter++
}
func main() {
counter := 0
for i := 0; i < 100; i++ {
go increment(&counter)
}
time.Sleep(time.Second)
fmt.Println("Counter:", counter)
}
وقتی دویدید go run -race main.go
، یک هشدار مانند:
WARNING: DATA RACE
Read at 0x00c0000a4010 by goroutine 6:
main.increment()
/path/to/main.go:9 +0x44
Previous write at 0x00c0000a4010 by goroutine 5:
main.increment()
/path/to/main.go:9 +0x54
این به ما می گوید که دو گوروتین در حال جمع شدن هستند counter
بشر ردیاب مسابقه مشکل را برطرف نمی کند – فقط به شما کمک می کند تا آن را پیدا کنید.
رفع شرایط مسابقه (پیش نمایش)
برای رفع شرایط مسابقه ، اغلب به هماهنگ سازی نیاز دارید. Go ابزارهایی مانند sync.Mutex
بشر در اینجا چگونه می توانیم آخرین مثال را اصلاح کنیم:
package main
import (
"fmt"
"sync"
"time"
)
func increment(counter *int, mu *sync.Mutex) {
mu.Lock() // Lock before changing counter
for i := 0; i < 10; i++ {
*counter++
}
mu.Unlock() // Unlock after
}
func main() {
counter := 0
var mu sync.Mutex
for i := 0; i < 5; i++ {
go increment(&counter, &mu)
}
time.Sleep(time.Second)
fmt.Println("Counter:", counter)
}
خروجی:
Counter: 50
حالا همیشه 50 است!
در Mutex
(کوتاه برای محرومیت متقابل) فقط یک به روزرسانی گوروتین را تضمین می کند counter
در یک زمان
دیگر شرایط مسابقه نیست.
ما در یک آموزش دیگر عمیق تر به اصلاحات شیرجه می زنیم ، اما این ایده اصلی را نشان می دهد.
پیچیدن
شرایط مسابقه هنگامی اتفاق می افتد که Goroutines با داده های مشترک بدون قوانین مبارزه می کند. شما دیده اید:
- یک برنامه ایمن تک رشته ای.
- یک مسابقه ساده با دو گوروتین.
- یک مسابقه بزرگتر با 100 گوروتین.
- نحوه تشخیص نژادها با
-race
بشر - یک نگاه دزدکی حرکت در رفع آن
Mutex
بشر
خودتان این مثالها را اجرا کنید.
اگر شما قادر به مشاهده شرایط مسابقه نیستید – فقط گورواتین ها/حلقه ها را افزایش دهید – مشکلات به زودی شروع به نمایش می کنند!
اعداد را تغییر دهید ، جوراب های بیشتری اضافه کنید و از ردیاب مسابقه استفاده کنید.
هرچه بیشتر آزمایش کنید ، بهتر خواهید فهمید که چگونه نژادها به کد شما می پردازند!