
- بواسطة x32x01 ||
الـ Data Race من أكتر الأخطاء اللي ممكن تدمر برامج الـ Concurrency من غير ما تحس.
تخيل عندك كذا Goroutine شغالين في نفس الوقت، وكلهم بيحاولوا يكتبوا في نفس المتغير... ساعتها النتيجة بتعتمد مش على الكود، لكن على مين سبق!
يعني إيه Data Race؟
الـ Data Race بتحصل لما أكتر من Thread أو Goroutine يحاولوا يعدّلوا على نفس البيانات في نفس اللحظة.
في الحالة دي، البرنامج ممكن يطلع نتائج مختلفة كل مرة تشغله فيها!
وده لأن العملية البسيطة دي:
مش خطوة واحدة زي ما شكلها بيقول، دي 3 خطوات:
لو حصل إن اتنين Goroutine نفذوا السطر ده مع بعض… هيقروا نفس القيمة، ويكتبوا نفس القيمة الجديدة، وساعتها هنخسر زيادة كاملة من العداد
مثال عملي بلغة Go
المفروض يطبع 1000، صح؟
لكن في الحقيقة هتلاقيه بيطبع رقم أقل، ومش ثابت كمان
وده لأننا عندنا Data Race حقيقية.
الحل السحري: Mutex
هنا بييجي دور الـ Mutex (اختصار لـ Mutual Exclusion).
فكر فيه كأنه “مفتاح غرفة البيانات” - اللي يمسكه هو الوحيد اللي يقدر يعدّل على البيانات في الوقت ده.
الكود بعد التصحيح:
النتيجة؟ 
العداد دايمًا هيوصل لـ 1000 لأن كل Goroutine بيستنى دوره في تعديل القيمة.
المفاجأة: مش دايمًا الغلط من الكود!
أحيانًا الـ Race Condition بتحصل مش بسبب خطأك، لكن بسبب نظام التشغيل (OS) أو المعالج (CPU).
نظام التشغيل ممكن يوقف الـ Thread بتاعك في أي لحظة (عشان يشغّل حاجة أهم مثلًا)، وده اسمه Context Switch.
المصيبة بتحصل لو Thread تاني غيّر نفس البيانات أثناء ما الأول متوقف… ولما الأول يرجع، بيكتب فوق التغيير الجديد!
الخلاصة
حتى لو الكود باين “صح” بالمنطق، ما تعتمدش على الحظ.
استخدم أدوات التزامن زي Mutex، أو Channels في Go، عشان تضمن إن كل حاجة بتتعمل بأمان.
الـ Data Race من النوع اللي مش بيظهر في الاختبارات - لكنه بيفجّرك في الإنتاج!
تخيل عندك كذا Goroutine شغالين في نفس الوقت، وكلهم بيحاولوا يكتبوا في نفس المتغير... ساعتها النتيجة بتعتمد مش على الكود، لكن على مين سبق!

يعني إيه Data Race؟
الـ Data Race بتحصل لما أكتر من Thread أو Goroutine يحاولوا يعدّلوا على نفس البيانات في نفس اللحظة.في الحالة دي، البرنامج ممكن يطلع نتائج مختلفة كل مرة تشغله فيها!
وده لأن العملية البسيطة دي:
Code:
counter++
مش خطوة واحدة زي ما شكلها بيقول، دي 3 خطوات:
- قراءة القيمة الحالية.
- زيادة القيمة بواحد.
- كتابة القيمة الجديدة.
لو حصل إن اتنين Goroutine نفذوا السطر ده مع بعض… هيقروا نفس القيمة، ويكتبوا نفس القيمة الجديدة، وساعتها هنخسر زيادة كاملة من العداد

مثال عملي بلغة Go
Code:
package main
import (
"fmt"
"sync"
)
func main() {
var counter int
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
counter++
wg.Done()
}()
}
wg.Wait()
fmt.Println("Counter:", counter)
}
لكن في الحقيقة هتلاقيه بيطبع رقم أقل، ومش ثابت كمان

وده لأننا عندنا Data Race حقيقية.
الحل السحري: Mutex
هنا بييجي دور الـ Mutex (اختصار لـ Mutual Exclusion).فكر فيه كأنه “مفتاح غرفة البيانات” - اللي يمسكه هو الوحيد اللي يقدر يعدّل على البيانات في الوقت ده.
الكود بعد التصحيح:

Code:
package main
import (
"fmt"
"sync"
)
func main() {
var counter int
var wg sync.WaitGroup
var mu sync.Mutex
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
mu.Lock()
counter++
mu.Unlock()
wg.Done()
}()
}
wg.Wait()
fmt.Println("Counter:", counter)
}

العداد دايمًا هيوصل لـ 1000 لأن كل Goroutine بيستنى دوره في تعديل القيمة.
المفاجأة: مش دايمًا الغلط من الكود!
أحيانًا الـ Race Condition بتحصل مش بسبب خطأك، لكن بسبب نظام التشغيل (OS) أو المعالج (CPU).نظام التشغيل ممكن يوقف الـ Thread بتاعك في أي لحظة (عشان يشغّل حاجة أهم مثلًا)، وده اسمه Context Switch.
المصيبة بتحصل لو Thread تاني غيّر نفس البيانات أثناء ما الأول متوقف… ولما الأول يرجع، بيكتب فوق التغيير الجديد!
الخلاصة
حتى لو الكود باين “صح” بالمنطق، ما تعتمدش على الحظ.استخدم أدوات التزامن زي Mutex، أو Channels في Go، عشان تضمن إن كل حاجة بتتعمل بأمان.
الـ Data Race من النوع اللي مش بيظهر في الاختبارات - لكنه بيفجّرك في الإنتاج!

التعديل الأخير: