سلسلة Swift Concurrency 05|الفرق بين الممثل وطرق الكتابة التقليدية الآمنة للخيط
الجهات الفاعلة ليست "أقفال سريعة". ما يغيرونه حقًا هو كيفية تنظيم الدولة المشتركة.
عندما تسمع Actor لأول مرة، ستفهمها دون وعي على أنها “أداة أمان للخيط مقدمة من Swift”.
هذا الفهم ليس خاطئًا تمامًا، ولكن إذا توقفت هنا، سيكون من السهل كتابة تعليمات برمجية “تستخدم ممثلين، لكن البنية لا تزال فوضوية”.
لأن أهم شيء في Actor هو أنه يغير الفكرة الافتراضية:
لا ينبغي كشف الحالة المشتركة القابلة للتغيير للجميع أولاً ثم إيجاد طرق لحمايتها؛ النهج الأكثر منطقية هو عزله أولاً ثم تحديد كيفية الوصول إليه من الخارج.
قد يبدو هذا بمثابة اختلاف في الصياغة، ولكنه في الواقع يمثل نقطة تحول في عادات تصميم التزامن.
1. أخطر شيء في التزامن هو الحالة المتغيرة المشتركة
تبدأ معظم أخطاء التزامن من:
- مهمتان لقراءة وكتابة نفس ذاكرة التخزين المؤقت في نفس الوقت
- لم تتم كتابة مهمة واحدة بعد، وتم بالفعل إصدار حكم لمهمة أخرى بناءً على القيمة القديمة.
- تقوم صفحات متعددة بتعديل الحالة العامة في نفس الوقت
- خدمة معينة مسؤولة عن تحديث الرموز المميزة واستهلاك الرموز المميزة وبث النتائج.
قد تظهر هذه المشاكل في أشكال عديدة، ولكن الأسباب الجذرية غالبًا ما تكون هي نفسها: **الحالة المشتركة القابلة للتغيير ليس لها حدود واضحة. **
بمجرد أن تصبح حدود الحالة غير واضحة، فإن أي تغيير في ترتيب جدولة المهام قد يؤدي إلى تضخيم الأخطاء.
2. مشكلة حلول سلامة الخيوط التقليدية هي أنها ليست مركزية بدرجة كافية.
تشمل الحلول التي استخدمناها بشكل شائع في الماضي ما يلي:
NSLock- قائمة الانتظار التسلسلية
- قفل القراءة والكتابة
- اتفاق الفريق على أنه “لا يمكن الوصول إلى هذا الكائن إلا في قائمة انتظار معينة”
لا يوجد خطأ في أي من هذه الحلول، ولا تزال العديد من الأنظمة الناضجة قيد الاستخدام المستقر حتى يومنا هذا. مشكلتهم الحقيقية هي:
** قواعد الوصول متناثرة بسهولة. **
في البداية ربما لا تزال تتذكر:
- يجب الوصول إلى ذاكرة التخزين المؤقت هذه بقفل
- لا يمكن تغيير هذا القاموس إلا في قائمة الانتظار التسلسلية
- لا يمكن قراءة وكتابة حالة معينة إلا في الموضوع الرئيسي
ولكن مع زيادة عدد نقاط الاتصال، سوف تصبح هذه القواعد مشوهة ببطء. الأمر الأكثر إزعاجًا بشأن أخطاء التزامن هو أنه لم يعد من الممكن في كثير من الأحيان التأكد من التزام الفريق بأكمله بصرامة بتلك القواعد المتناثرة.
3. الفكرة الأساسية للممثل هي “تقليل المشاركة أولاً”
هذه هي النقطة الأكثر أهمية التي تستحق التكرار.
الفكرة التقليدية هي أشبه بما يلي:
- احصل أولاً على حالة مشتركة
- ثم فكر في طرق لحمايته من خلال الأقفال وقوائم الانتظار والاصطلاحات
فكرة الممثل أقرب:
- لا ينبغي الوصول إلى هذه الحالة بشكل تعسفي.
- أضعه في حدود العزلة أولاً
- لا يمكن للعالم الخارجي التفاعل معه إلا من خلال الواجهة التي يتم التحكم فيها
أكبر تغيير يجلبه هذا هو أننا مجبرون على التفكير في ملكية الحالة، بدلاً من التسليم بحقيقة أنه يمكن لأي شخص لمسها مباشرة.
على سبيل المثال، إذا كان منسق تحديث الرمز المميز مجرد كائن عالمي يحتوي على مجموعة من الأقفال، فإن العقلية الافتراضية هي “الجميع يستخدم هذه الحالة، ويجب علي حمايتها”.
إذا كان Actor، فإن العقلية الافتراضية ستصبح “هذه الحالة تدار بواسطتها، ويمكن للآخرين فقط أن يطلبوا منها النتائج أو يرسلوا الأوامر”.
هكذا يتغير التصميم.
4. يعتبر أسلوب العزل هذا أكثر ملاءمة للمشاريع المعقدة
لأن أكثر ما تخشاه المشاريع المعقدة هو انتشار القواعد.
بمجرد حظر الوصول إلى الحالة بواسطة Actor، سيتم الكشف عن العديد من المشكلات في وقت سابق:
-من يتحكم في هذه البيانات؟
- هل المتطلب الخارجي لقطة أم قيمة مشتقة أم عملية حتمية؟
- ما هي العمليات التي يجب أن تحتوي على دلالات تسلسلية وأيها مجرد استعلامات للقراءة فقط
غالبًا ما تم تأجيل هذه المشكلات في النموذج القديم، لأن الجميع اختاروا “المشاركة أولاً، ثم القفل في حالة وجود مشاكل”. من ناحية أخرى، يفرض الممثلون تصميم الحدود في وقت مبكر.
لذلك، أفضّل التفكير في Actor باعتباره أداة تصميم متزامنة بدلاً من مجرد أداة أمان للسلسلة.
5. ما هو أفضل مكان لتعيين الممثل؟
ليست كل الأشياء تستحق أن تتحول إلى ممثلين. وهي مناسبة أكثر للأدوار التي تتوفر فيها الخصائص التالية في نفس الوقت:
- هل لديك حالة قابلة للتغيير المشتركة
- سيتم الوصول إليها بشكل متزامن من خلال مهام متعددة
- الاتساق مهم
- يجب تنسيق طرق الوصول مركزيًا
تشمل الأمثلة النموذجية ما يلي:
- منسق ذاكرة التخزين المؤقت للصور
- منسق تحديث الرمز المميز
- تنزيل مركز تسجيل المهام
- نوع من الوصول إلى الموارد العالمية
- مركز الرسائل الذي يتطلب الاستهلاك التسلسلي للأحداث
ما تشترك فيه هذه الكائنات هو أنها مراكز حالة يتم مشاركتها عبر المهام وتكون عرضة لمشاكل التزامن.
إذا كانت مجرد خدمة وظيفية خالصة، أو كائن حالة يتم استخدامه لفترة وجيزة فقط داخل صفحة واحدة، فقد لا تكون هناك حاجة إلى ممثل.
6. ما هو الفرق الأساسي بين الممثل و"إضافة قفل للفصل بأكمله"؟
ظاهريًا، يمكنهم جميعًا القيام “بمهام متعددة دون تغيير الحالة”. لكن الخبرة الهندسية تختلف اختلافا كبيرا.
عند قفل فصل دراسي بأكمله، تكون الأسئلة الشائعة هي:
- لا يزال بإمكان المتصل الحصول على الكثير من التفاصيل الداخلية
- يمكن أن تخرج تفاصيل الأقفال عن نطاق السيطرة بسهولة
- تستدعي بعض الطرق موارد مزامنة أخرى، مما يشكل تداخلًا معقدًا.
- لا يزال بإمكان أعضاء الفريق تجاوز القواعد والوصول مباشرة إلى الحالة التي لا ينبغي لهم الوصول إليها
فالفاعل، على المستوى الدلالي على الأقل، يعبر بوضوح عن:
- لا تتم مشاركة هذه الحالة بحرية
- يحتاج الوصول إلى العزلة المتقاطعة إلى المرور بشكل صريح عبر الحدود غير المتزامنة
- الوصول إلى هذه الحالة هو في حد ذاته سلوك مقيد
بمعنى آخر، لا “يساعد الممثلون في إضافة الحماية” فحسب، بل يجعلون أيضًا طرق الوصول غير الآمنة أكثر صعوبة في الكتابة بشكل عرضي.
7. لا يستطيع الممثل حل أي شيء
ويجب توضيح هذا. عندما تتعلم هذا المحتوى لأول مرة، سيكون لديك وهم بأن سلامة الخيط ستترك له في المستقبل.
في الواقع، لا يحل الممثلون سوى نوع واحد من المشاكل: عزل الحالة المشتركة.
لا يحل:
- هل تسلسل الأعمال في حد ذاته معقول؟
- هل يجب أن تستمر المهام القديمة بعد مغادرة الصفحة؟
- ما إذا كانت النتيجة قد انتهت
- هل تم رسم حدود الدولة بشكل خاطئ؟
على سبيل المثال، إذا تم تحويل ذاكرة التخزين المؤقت لنتائج البحث إلى ممثل، لكن الصفحة لا تزال تسمح للطلبات القديمة بالكتابة فوق نتائج الكلمات الرئيسية الجديدة، فسيظل الخطأ موجودًا. لأن المشكلة هنا هي أن “صلاحية النتيجة” لم يتم تصميمها في المقام الأول.
لذا فإن الممثلين مهمون، لكنهم ليسوا علاجًا سحريًا للتزامن.
8. الاتجاهان الأكثر إساءة استخدامًا للممثل
1. حاول أن تناسب كل شيء مع الممثل
بمجرد اعتبار الممثلين “أكثر أمانًا طالما تم استخدامهم”، سيبدأون في المبالغة في التغليف:
- الحالة التي من الواضح أنها تُستخدم فقط في سياق مفرد يتم تجميعها أيضًا كممثل
- المنطق الأكثر ملاءمة لمعالجة الوظائف الخالصة يتم فرضه أيضًا على الممثل
- في النهاية، النظام بأكمله مليء بحدود الوصول غير المتزامنة، ويصبح الهيكل أثقل.
المزيد من الممثلين ليس دائمًا أفضل. المفتاح هو وضعها حيث تكون الحالة المشتركة مطلوبة حقًا لعزلها.
2. تعامل مع الممثلين على أنهم “دليل أمني”
بمجرد إضافة الممثلين إلى بعض التعليمات البرمجية، سيفترض الفريق أن هذا الجزء من التعليمات البرمجية “آمن للسلسلة”. لكن العديد من الأخطاء تأتي من:
- تصميم خاطئ لدورة الحياة
- تدفق حالة خاطئة
- علاقة مهمة خاطئة
يمكن للممثل أن يضمن “عدم العبث بالتزامن والتطرق إلى حالتي الداخلية”، لكنه لا يستطيع أن يضمن “أن عملية العمل صحيحة”.
9. سؤال حكم عملي أكثر
إذا كنت مترددًا فيما إذا كان ينبغي تصميم كائن ما كممثل، أقترح عليك ألا تسأل “هل سيكون الخيط غير آمن” أولاً، ولكن اسأل أولاً:
- هل تحتوي على حالة مشتركة مهمة قابلة للتغيير بداخلها؟
- هل سيتم الوصول إلى هذه الحالة من خلال مهام متعددة في نفس الوقت؟
- هل أريد أن يتفاعل العالم الخارجي معه فقط من خلال واجهات يتم التحكم فيها؟
- إذا لم تتم إضافة حدود العزل، فهل سيكون من السهل على الفريق إساءة استخدامها في المستقبل؟
إذا كانت الإجابة على معظم هذه الأسئلة هي “نعم”، فمن المحتمل أن يكون هذا مناسبًا للممثل.
10. الاستنتاج: القيمة الحقيقية للممثل هي تغيير الحالة المشتركة القابلة للتغيير من التعرض الافتراضي إلى العزل الافتراضي.
ولكي أختصر الأمر أقول:
Actorما تغير حقًا هو أن “الحالة المشتركة القابلة للتغيير لم تعد مكشوفة للجميع بشكل افتراضي.”
والتفكير التقليدي يشبه إلى حد كبير: المشاركة أولاً، ثم الحماية. فكرة الممثل هي: العزل أولاً، ومن ثم تحديد كيفية الوصول.
في الأنظمة المتزامنة المعقدة، غالبًا ما يكون هذا التغيير في التفكير أكثر أهمية من “أداة إضافية لسلامة الخيط”.
What to read next
Want more posts about Swift Concurrency?
Posts in the same category are usually the best next step for reading more on this topic.
View same categoryWant to keep following #Swift Concurrency?
Tags are useful for related tools, specific problems, and similar troubleshooting notes.
View same tagWant to explore another direction?
If you are not sure what to read next, return to the homepage and start from categories, topics, or latest updates.
Back home