هل من الأفضل استخدام "مزامنة" TThread أو استخدام رسائل النافذة لـ IPC بين الخيط الرئيسي والفرعي؟

StackOverflow https://stackoverflow.com/questions/1806339

  •  05-07-2019
  •  | 
  •  

سؤال

لدي تطبيق بسيط لواجهة المستخدم الرسومية VCL متعدد الخيوط مكتوب باستخدام دلفي 2007.أقوم ببعض المعالجة في عدة سلاسل فرعية (ما يصل إلى 16 سلسلة متزامنة) والتي تحتاج إلى تحديث عنصر تحكم الشبكة في النموذج الرئيسي (ببساطة نشر السلاسل على الشبكة).لا يتحدث أي من الخيوط الفرعية مع بعضها البعض على الإطلاق.

كان تصميمي الأولي يتضمن الاتصال "مزامنة" TThread لتحديث نموذج التحكم في الشبكة داخل مؤشر الترابط قيد التشغيل حاليًا.ومع ذلك، فأنا أفهم أن استدعاء Synchronize يتم تنفيذه بشكل أساسي كما لو كان الخيط الرئيسي عند الاتصال به.مع تشغيل ما يصل إلى 16 مؤشر ترابط في وقت واحد (وتستغرق معظم معالجة مؤشر الترابط الفرعي من أقل من ثانية واحدة إلى 10 ثوانٍ تقريبًا) هل ستكون رسائل النافذة تصميمًا أفضل؟

لقد نجحت في هذه المرحلة حيث ينشر الخيط الفرعي رسالة Windows (تتكون من سجل من عدة سلاسل) ويكون للخيط الرئيسي مستمع ويقوم ببساطة بتحديث الشبكة عند تلقي رسالة.

هل هناك أي آراء حول أفضل طريقة لـ IPC في هذه الحالة؟رسائل النافذة أم "المزامنة"؟

إذا كنت أستخدم رسائل النافذة، فهل تقترح التفاف الكود الذي أقوم بنشره على الشبكة في كتلة TCriticalSection (إدخال ومغادرة)؟أم أنني لن أحتاج إلى القلق بشأن سلامة سلسلة الرسائل لأنني أكتب إلى الشبكة في سلسلة الرسائل الرئيسية (على الرغم من وجودها ضمن وظيفة معالج رسائل النافذة)؟

هل كانت مفيدة؟

المحلول

يحرر:

يبدو أن العديد من تفاصيل التنفيذ قد تغيرت منذ دلفي 4 و5 (إصدارات دلفي التي لا أزال أستخدمها في معظم أعمالي)، وقد علق ألين باور بما يلي:

منذ D6، لم يعد TThread يستخدم SendMessage بعد الآن.يستخدم قائمة انتظار عمل آمنة للخيط حيث يتم وضع "العمل" المخصص للخيط الرئيسي.يتم نشر رسالة على الموضوع الرئيسي للإشارة إلى أن العمل متاح وأن مؤشر الترابط في الخلفية محظور في حدث ما.عندما تكون حلقة الرسالة الرئيسية على وشك الخمول، فإنها تستدعي "CheckSynchronize" لمعرفة ما إذا كان هناك أي عمل في الانتظار.إذا كان الأمر كذلك، فإنه يعالج ذلك.بمجرد اكتمال عنصر العمل، يتم تعيين الحدث الذي تم فيه حظر مؤشر ترابط الخلفية للإشارة إلى الاكتمال.تم تقديمه في الإطار الزمني D2006، وتمت إضافة طريقة TThread.Queue التي لا تمنع.

شكرا على التصحيح.لذا خذ التفاصيل في الإجابة الأصلية بحذر.

ولكن هذا لا يؤثر حقا على النقاط الأساسية.ما زلت أصر على أن الفكرة برمتها Synchronize() معيبًا بشكل قاتل، وسيكون هذا واضحًا في اللحظة التي يحاول فيها المرء الاحتفاظ بالعديد من مراكز الآلة الحديثة مشغولة.لا تقم "بمزامنة" المواضيع الخاصة بك، دعها تعمل حتى تنتهي.حاول تقليل كافة التبعيات بينهما.خصوصا عند تحديث واجهة المستخدم الرسومية هناك على الاطلاق لا سبب الانتظار حتى يكتمل هذا.سواء Synchronize() الاستخدامات SendMessage() أو PostMessage(), ، فإن حاجز الطريق الناتج هو نفسه.


ما تقدمه هنا ليس بديلاً على الإطلاق، كما Synchronize() الاستخدامات SendMessage() داخليا.لذا فإن السؤال المطروح هو ما هو السلاح الذي تريد استخدامه لإطلاق النار على قدمك.

Synchronize() لقد كان معنا منذ تقديم TThread في Delphi 2 VCL، وهو أمر مؤسف حقًا لأنه أحد أكبر ميزات التصميم الخاطئة في VCL.

كيف يعمل؟يستخدم أ SendMessage() استدعاء النافذة التي تم إنشاؤها في سلسلة المحادثات الرئيسية، وتعيين معلمات الرسالة لتمرير عنوان أسلوب كائن بدون معلمات ليتم استدعاؤه.نظرًا لأن رسائل Windows ستتم معالجتها فقط في مؤشر الترابط الذي أنشأ نافذة الوجهة ويقوم بتشغيل حلقة الرسائل الخاصة بها، فسيؤدي ذلك إلى تعليق مؤشر الترابط، والتعامل مع الرسالة في سياق مؤشر ترابط VCL الرئيسي، واستدعاء الطريقة، واستئناف مؤشر الترابط فقط بعد الطريقة تم الانتهاء من التنفيذ.

إذن ما هو الخطأ في ذلك (وما هو الخطأ بالمثل في استخدام SendMessage() مباشرة)؟عدة أشياء:

  • إن إجبار أي مؤشر ترابط على تنفيذ تعليمات برمجية في سياق مؤشر ترابط آخر يفرض مفتاحين لسياق مؤشر الترابط، مما يؤدي إلى حرق دورات وحدة المعالجة المركزية دون داع.
  • بينما يقوم مؤشر ترابط VCL بمعالجة الرسالة لاستدعاء الطريقة المتزامنة، فإنه لا يمكنه معالجة أي رسالة أخرى.
  • عندما يستخدم أكثر من موضوع هذه الطريقة فسوف يفعلون ذلك الجميع كتلة وانتظر Synchronize() أو SendMessage() لكي ترجع.وهذا يخلق عنق الزجاجة العملاقة.
  • هناك طريق مسدود في انتظار أن يحدث.إذا كان الخيط يدعو Synchronize() أو SendMessage() أثناء الاحتفاظ بكائن المزامنة، يحتاج مؤشر ترابط VCL أثناء معالجة الرسالة إلى الحصول على نفس كائن المزامنة الذي سيقفله التطبيق.
  • يمكن قول الشيء نفسه عن استدعاءات واجهة برمجة التطبيقات (API) التي تنتظر مؤشر الترابط - باستخدام WaitForSingleObject() أو WaitForMultipleObjects() سيؤدي عدم وجود بعض الوسائل لمعالجة الرسائل إلى طريق مسدود إذا كان الخيط يحتاج إلى هذه الطرق "للمزامنة" مع الخيط الآخر.

إذن ما الذي يجب استخدامه بدلاً من ذلك؟عدة خيارات، سأصف بعضها:

  • يستخدم PostMessage() بدلاً من SendMessage() (أو PostThreadMessage() إذا كان كلا الخيطين ليسا خيط VCL).ومع ذلك، فمن المهم عدم استخدام أي بيانات في معلمات الرسالة التي لن تكون صالحة عند وصول الرسالة، حيث لا تتم مزامنة خيط الإرسال والاستقبال على الإطلاق، لذلك يجب استخدام بعض الوسائل الأخرى للتأكد من أن أي سلسلة أو مرجع الكائن أو جزء من الذاكرة لا يزال صالحًا عند معالجة الرسالة، على الرغم من أن مؤشر ترابط الإرسال قد لا يكون موجودًا بعد الآن.

  • قم بإنشاء هياكل بيانات آمنة للخيط، وقم بوضع البيانات عليها من سلاسل العمليات العاملة الخاصة بك، واستهلكها من الخيط الرئيسي.يستخدم PostMessage() فقط لتنبيه مؤشر ترابط VCL بأن البيانات الجديدة قد وصلت لتتم معالجتها، ولكن لا تنشر رسائل في كل مرة.إذا كان لديك دفق مستمر من البيانات، فيمكنك أيضًا الحصول على استطلاع VCL للبيانات (ربما باستخدام مؤقت)، ولكن هذه نسخة الرجل الفقير فقط.

  • لا تستخدم الأدوات ذات المستوى المنخفض على الإطلاق، بعد الآن.إذا كنت تستخدم دلفي 2007 على الأقل، فقم بتنزيل ملف OmniThreadLibrary والبدء في التفكير من حيث المهام، وليس المواضيع.تحتوي هذه المكتبة على الكثير من التسهيلات لتبادل البيانات بين المواضيع والمزامنة.كما أن لديها تطبيقًا لتجمع الخيوط، وهو أمر جيد - لا يعتمد عدد الخيوط التي يجب عليك استخدامها على التطبيق فحسب، بل أيضًا على الجهاز الذي يعمل عليه، لذلك يمكن اتخاذ العديد من القرارات في وقت التشغيل فقط.سيسمح لك OTL بتشغيل المهام على سلسلة رسائل مجمعة، بحيث يمكن للنظام ضبط عدد سلاسل الرسائل المتزامنة في وقت التشغيل.

يحرر:

عند إعادة القراءة أدركت أنك لا تنوي الاستخدام SendMessage() لكن PostMessage() - حسنًا، بعض ما سبق لا ينطبق إذن، لكنني سأتركه في مكانه.ولكن هناك نقاط أخرى في سؤالك أريد أن أتطرق إليها:

مع تشغيل ما يصل إلى 16 مؤشر ترابط في وقت واحد (وتستغرق معظم معالجة مؤشر الترابط الفرعي من أقل من ثانية واحدة إلى 10 ثوانٍ تقريبًا) هل ستكون رسائل النافذة تصميمًا أفضل؟

إذا قمت بنشر رسالة من كل موضوع مرة كل ثانية أو حتى لفترة أطول، فالتصميم جيد.ما لا يجب عليك فعله هو نشر مئات أو أكثر من الرسائل لكل سلسلة رسائل في الثانية، لأن قائمة انتظار رسائل Windows لها طول محدود ويجب ألا تتداخل الرسائل المخصصة مع معالجة الرسائل العادية كثيرًا (سيبدأ برنامجك في الظهور غير مستجيب).

حيث يقوم الخيط الفرعي بنشر رسالة Windows (تتكون من سجل من عدة سلاسل)

لا يمكن أن تحتوي رسالة النافذة على سجل.يحمل معلمتين، واحدة من النوع WPARAM, ، والآخر من النوع LPARAM.يمكنك فقط إرسال مؤشر إلى مثل هذا السجل إلى أحد هذه الأنواع، لذا يجب إدارة عمر السجل بطريقة ما.إذا قمت بتخصيصه ديناميكيًا، فستحتاج إلى تحريره أيضًا، وهو ما يكون عرضة للأخطاء.إذا قمت بتمرير مؤشر إلى سجل في المكدس أو إلى حقل كائن، فأنت بحاجة إلى التأكد من أنه لا يزال صالحًا عند معالجة الرسالة، وهو أمر أكثر صعوبة بالنسبة للرسائل المنشورة مقارنة بالرسائل المرسلة.

هل تقترح تغليف الكود الذي أقوم بنشره على الشبكة في كتلة TCriticalSection (إدخال ومغادرة)؟أم أنني لن أحتاج إلى القلق بشأن سلامة سلسلة الرسائل لأنني أكتب إلى الشبكة في سلسلة الرسائل الرئيسية (على الرغم من وجودها ضمن وظيفة معالج رسائل النافذة)؟

ليست هناك حاجة للقيام بذلك، كما PostMessage() ستعود المكالمة فورًا، لذا لا يلزم إجراء مزامنة عند هذه النقطة.بالتأكيد سوف تحتاج إلى القلق بشأن سلامة الخيط، للأسف لا يمكنك معرفة ذلك متى.يجب عليك التأكد من أن الوصول إلى البيانات آمن من خلال سلاسل الرسائل دائماً قفل البيانات للوصول إليها باستخدام كائنات المزامنة.لا توجد حقًا طريقة لتحقيق ذلك بالنسبة للسجلات، حيث يمكن دائمًا الوصول إلى البيانات مباشرةً.

نصائح أخرى

وراجع للشغل، يمكنك أيضا استخدام TThread.Queue() بدلا من ذلك من TThread.Synchronize() . Queue() هو الإصدار المتزامن، فإنه لا يمنع موضوع الدعوة:

و(Queue متاح منذ D8).

وأنا أفضل Synchronize() أو Queue()، لأنه من الأسهل بكثير لفهم (للمبرمجين الآخرين) وأفضل OO من رسالة واضحة الإرسال (مع عدم وجود الرقابة عن ذلك أو قادرا على تصحيح ذلك!)

وبينما أنا متأكد من أن هناك طريقة صحيحة وطريقة خاطئة. لقد قانون مكتوب باستخدام كل الأساليب واحد وأظل العودة إلى هو الأسلوب SendMessage ولست متأكدا لماذا.

إن استخدام SendMessage مقابل تزامن لا حقا جعل أي فرق. كل من يعمل في الأساس نفسه. أعتقد أن السبب وأظل باستخدام SendMessage هو أن أرى قدرا أكبر من السيطرة، ولكن أنا لا أعرف.

وروتين SendMessage يتسبب في موضوع الدعوة إلى وقفة وانتظر حتى انتهاء نافذة الوجهة معالجة الرسالة المرسلة. بسبب هذا إمكانية المزامنة في موضوع التطبيق الرئيسي أساسا إلى موضوع الطفل الذي يدعو إلى مدة المكالمة. أنت لا تحتاج إلى استخدام مقطع حرج في معالج ويندوز الرسالة.

ونقل البيانات هو في الأساس وسيلة واحدة، من موضوع الدعوة إلى موضوع التطبيق الرئيسي. يمكنك إرجاع قيمة نوع عدد صحيح في message.result ولكن لا شيء يشير إلى كائن ذاكرة في مؤشر الترابط الرئيسي.

ومنذ المواضيع هما "مزامنتها" أن تلك النقطة وموضوع التطبيقات الرئيسية ويرتبط حاليا حتى الاستجابة لSendMessage ثم أنت أيضا لا داعي للقلق بشأن المواضيع الأخرى القادمة من والتحطيم البيانات الخاصة بك في الوقت نفسه زمن. لذلك لا كنت لا داعي للقلق حول استخدام أقسام الحرجة أو أنواع أخرى من تدابير السلامة الموضوع.

لأشياء بسيطة يمكنك تحديد رسالة واحدة (wm_threadmsg1) واستخدام الحقول wparam وlparam لنقل الرسائل (صحيح) حالة ذهابا وإيابا. لمزيد من الأمثلة المعقدة يمكنك تمرير سلسلة بتمريرها عبر lparam وبتلبيس مرة أخرى إلى longint. A-لا longint (نوع PChar (myvar)) أو استخدام pwidechar إذا كان لديك باستخدام D2009 أو الأحدث.

إذا كنت قد حصلت عليه بالفعل العمل مع وسائل تزامن ثم انني لن تقلق بشأن إعادة صياغة لإجراء تغيير.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top