سؤال

ما هي الوحدات النمطية المستخدمة لكتابة تطبيقات متعددة الخيوط في بيثون؟ أنا أدرك آليات التزامن الأساسية التي توفرها اللغة وأيضا بيثون بلا تكدس, ، ولكن ما هي نقاط القوة والضعف لديك؟

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

المحلول

من أجل زيادة التعقيد:

استخدم ال وحدة الخيوط

الايجابيات:

  • من السهل حقا تشغيل أي وظيفة (أي مستدلة في الواقع) في مؤشر ترابطها الخاص.
  • البيانات المشاركة إذا لم تكن سهلة (الأقفال ليست سهلة أبدا :)، بسيطة على الأقل.

سلبيات:

  • كما ذكر بواسطة juergen لا يمكن الوصول إلى مؤشرات الترابط Python في الواقع حالة الوصول بشكل متزامن في المترجم (هناك قفل واحد كبير، السمعة فوري العالمي قفل.) ما يعنيه ذلك في الممارسة العملية أن مؤشرات الترابط مفيدة لمهام I / O المرتبطة (الشبكات، الكتابة إلى القرص، وما إلى ذلك)، ولكن ليس مفيدا على الإطلاق للقيام بحسوبة متزامنة.

استخدم ال multiprocessing. وحدة

في حالة الاستخدام البسيط هذا يبدو تماما مثل استخدام threading ما عدا كل مهمة يتم تشغيلها في عملية خاصة بها وليس موضوعها الخاص. (تقريبا حرفيا: إذا كنت تأخذ مثال إيلي, واستبدال threading مع multiprocessing, Thread, ، مع Process, ، و Queue (الوحدة) مع multiprocessing.Queue, ، يجب أن تعمل على ما يرام.)

الايجابيات:

  • التزامن الفعلي لجميع المهام (لا قفل مترجم عالمي).
  • المقاييس إلى عدة معالجات، يمكن حتى الحجم إلى المتعدد الآلات.

سلبيات:

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

استخدام نموذج الحدث، مثل ملتوية

الايجابيات:

  • تحصل على رقابة دقيقة للغاية على الأولوية، حيث ينفذ متى.

سلبيات:

  • حتى مع مكتبة جيدة، عادة ما تكون البرمجة غير المتزامنة أكثر صعوبة من البرمجة الخيوط، من الصعب على حد سواء من حيث فهم ما من المفترض أن يحدث ذلك ومن حيث تصحيح الأخطاء ما يحدث بالفعل.

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

نصائح أخرى

لقد حصلت بالفعل على مجموعة متنوعة عادلة من الإجابات، من "خيوط وهمية" على طول الطريق إلى الأطر الخارجية، لكنني رأيت لا أحد يذكر Queue.Queue - الصلصة السرية "من خيوط cpython.

للتوسع: طالما أنك لا تحتاج إلى تداخل معالجة Pure-Python CPU - الثقيلة (في هذه الحالة تحتاجها multiprocessing - لكنه يأتي بمفرده Queue التنفيذ، أيضا، حتى تتمكن من إجراء بعض الحذرات اللازمة تطبيق المشورة العامة التي أعطيتها ؛-)، مدمج بيثون threading سوف تفعل ... ولكن سوف تفعل ذلك أفضل بكثير إذا كنت تستخدمه معروضة, ، على سبيل المثال، على النحو التالي.

"تنسى" الذاكرة المشتركة، من المفترض أن تكون زائد رئيسي من الخيوط مقابل متعدد المعالجات - لا يعمل بشكل جيد، لا يتجاوز جيدا، لا يملك أبدا. استخدم الذاكرة المشتركة فقط لهياكل البيانات التي تم إعدادها مرة واحدة قبل أنت تفرخ خيوط فرعية ولم تتغير بعد ذلك - لكل شيء آخر، وجعل غير مرتبطة الموضوع المسؤول عن هذا المورد، والتواصل مع هذا الموضوع عبر Queue.

تكريس مؤشر ترابط متخصص لكل مورد كنت تعتقد عادة للحماية عن طريق الأقفال: هيكل بيانات قابل للتغيير أو مجموعة متماسكة منها، وهو اتصال بعملية خارجية (DB، خادم XMLRPC، إلخ)، ملف خارجي، إلخ، إلخ . احصل على تجمع موضوع صغير يجري للمهام للأغراض العامة التي لا تملك أو تحتاج إلى مورد مخصص لهذا النوع - لا سوف تفرخ المواضيع كما ومتى هناك حاجة، أو النفقات العامة لتحويل الخيوط تغمرك.

التواصل بين مؤلمين هو دائما عبر Queue.Queue - شكل من أشكال تمرير الرسالة، وهي الأساس العاطل الوحيد لمتقليل المعالجات (إلى جانب ذاكرة المعاملات، والذي يعد ولكن لا أعرف أي تطبيقات تستحق الإنتاج إلا في Haskell).

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

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

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

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

ولكن، أدرك الملتوية ليست للجميع - "موارد تكريس أو تجمع، استخدم قائمة الانتظار، لا تفعل أي شيء يحتاج أبدا إلى قفل أو غموض Guido، أي إجراء مزامنة أكثر تقدما، مثل النهج الإشعاعي أو الحالة" لا يزال يستخدم حتى لو كنت لا تستطيع التفاف رأسك حول منهجيات ASYNC التي يحركها الأحداث، وستظل تقديم المزيد من الموثوقية والأداء أكثر من أي نهج خيوط غير قابل للتطبيق على نطاق واسع تعثرت عليه.

ذلك يعتمد على ما تحاول القيام به، لكنني جزئي فقط لاستخدام threading الوحدة النمطية في المكتبة القياسية لأنه يجعل من السهل حقا أن تأخذ أي وظيفة وتشغيلها فقط في مؤشر ترابط منفصل.

from threading import Thread

def f():
    ...

def g(arg1, arg2, arg3=None):
    ....

Thread(target=f).start()
Thread(target=g, args=[5, 6], kwargs={"arg3": 12}).start()

وما إلى ذلك وهلم جرا. غالبا ما يكون لدي إعداد منتج / مستهلك باستخدام قائمة انتظار متزامنة مقدمة من Queue وحدة

from Queue import Queue
from threading import Thread

q = Queue()
def consumer():
    while True:
        print sum(q.get())

def producer(data_source):
    for line in data_source:
        q.put( map(int, line.split()) )

Thread(target=producer, args=[SOME_INPUT_FILE_OR_SOMETHING]).start()
for i in range(10):
    Thread(target=consumer).start()

Kamaelia. هو إطار ثعبان لبناء التطبيقات مع الكثير من العمليات التواصل.

(مصدر: kamaelia.org.) Kamaelia - التزامن مفيد وممتع

في Kamaelia تقوم ببناء أنظمة من مكونات بسيطة تتحدث مع بعضها البعض. وبعد هذه التطوير السرعات، وصيانة الإيدز بشكل كبير وتعني أيضا بناء البرمجيات المتزامنة بشكل طبيعي. وبعد من المقصود أن يكون متاحا أي المطور، بما في ذلك المبتدئين. كما أنه يجعلها ممتعة :)

أي نوع من الأنظمة؟ خوادم الشبكة والعملاء وتطبيقات سطح المكتب والألعاب القائم على Pygame و Systems Transcode وخطوط الأنابيب وأنظمة التلفزيون الرقمية والأدوات البريدية والأدوات التعليمية وأدوات التدريس والمبلغ العادي أكثر :)

إليك فيديو من Pycon 2009. يبدأ بمقارنة Kamaelia ملتوية و الموازية بيثون ثم يعطي أيدي على مظاهرة كامايليا.

التزامن سهل مع Kamaelia - الجزء 1 (59:08)
التزامن سهل مع Kamaelia - الجزء 2 (18:15)

فيما يتعلق كامايليا، فإن الإجابة أعلاه لا تغطي حقا الاستفادة هنا. يوفر نهج Kamaelia واجهة موحدة، غير مثالية غير مثالية، للتعامل مع المواضيع والمولدات والعمليات في نظام واحد للتزامن.

في الأساس يوفر استعارة من الشيء الجاري الذي يحتوي على صناديق الوارد وصندوق خارجي. يمكنك إرسال رسائل إلى Outboxes، وعندما سلكي معا، تدفق الرسائل من صناديق Outboxes إلى علبة الوارد. لا يزال هذا الاستعارة / API هو نفسه سواء كنت تستخدم المولدات أو المواضيع أو العمليات، أو التحدث إلى أنظمة أخرى.

من المقرر أن يكون الجزء "غير المثالي" بسبب السكر النحوي الذي لا يضاف إليه حتى الآن لصناديق الوارد والصناديق البيئية (على الرغم من أن هذا قيد المناقشة) - هناك تركيز على السلامة / قابلية الاستخدام في النظام.

أخذ مثال المستهلك المنتج باستخدام الخيوط العارية أعلاه، يصبح هذا في كامايليا:

Pipeline(Producer(), Consumer() )

في هذا المثال، لا يهم إذا كانت هذه مكونات خيوط أو غير ذلك، فإن الفرق الوحيد هو بين منظور الاستخدام هو الأساس الأساسي للمكون. مكونات المولدات تتواصل باستخدام القوائم والمكونات الخيوط باستخدام قائمة الانتظار.

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

إذا كنت تستخدم خيوط وأقفال عارية في كل مكان تعمل عموما على افتراض ذلك عند كتابة التعليمات البرمجية التي لن تقوم بأي أخطاء. في حين أننا نطمح جميعا إلى ذلك، فمن النادر جدا أن يحدث ذلك. عن طريق إفتام سلوك القفل في مكان واحد تقوم بتبسيط حيث يمكن أن تخطئ الأمور. (مساعدة معالجات السياق، ولكن لا تساعد في التحديثات العرضية خارج معالج السياق)

من الواضح أنه لا يمكن كتابة كل قطعة من التعليمات البرمجية بأنها رسالة تمر وسلسلة مشتركة وهذا هو السبب في أن Kamaelia لديها أيضا ذاكرة معاملات البرامج البسيطة (STM)، وهي فكرة أنيقة حقا مع اسم سيء - إنه أكثر مثل التحكم في الإصدار للمتغيرات - أي تحقق من بعض المتغيرات وتحديثها وارتكبها. إذا حصلت على اشتباك أنت شطف وتكرر.

الروابط ذات الصلة:

على أي حال، آمل أن تكون إجابة مفيدة. FWIW، السبب الأساسي وراء إعداد Kamaelia هو جعل التزامن أكثر أمانا وسهولة الاستخدام في أنظمة بيثون، دون ذيل يهز الكلب. (أي دلو كبير من المكونات

أستطيع أن أفهم لماذا تم تعديل إجابة Kamaelia الأخرى، لأنه حتى بالنسبة لي تبدو أشبه بالإجابة. كمؤلف Kamaelia من الجميل أن نرى الحماس على الرغم من أن هذا يتضمن محتوى أكثر صلة قليلا :-)

وهذه طريقة قولها، يرجى أخذ تحذير أن هذه الإجابة بحكم تعريفها، ولكن بالنسبة لي، هدف Kamaelia هو محاولة لف ما هو أفضل الممارسات IMO أفضل الممارسات. أود أن أقترح تجربة عدد قليل من الأنظمة، ورؤية ما يناسبك. (أيضا إذا كان هذا غير مناسب لتراجع المكدس، آسف - أنا جديد في هذا المنتدى :-)

أود استخدام microthreads (Tasklets) من الثعبان الزائد، إذا كان علي استخدام المواضيع على الإطلاق.

تقوم لعبة عبر الإنترنت بأكملها (Massivly Multiplayer) ببناء مبدأ تكثيف ومتعددة متعددة - نظرا لأن الأصل هو مجرد إبطاء خاصية لعبة Massivly Multiplayer للعبة.

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

مع CPYTHON، بدلا من استخدام عمليات منفصلة إن أمكن.

إذا كنت ترغب حقا في الحصول على يديك القذرة، فيمكنك أن تجرب استخدام المولدات إلى coroutines وهمية. وبعد ربما لا تكون الأكثر كفاءة من حيث العمل المعني، لكن Coroutines تقدم لك سيطرة رائعة جدا على تعاوني تعدد المهام بدلا من تعدد المهام الوقائي الذي ستجده في مكان آخر.

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

ربما يكون العيب الأكبر في تعدد المهام التعاوني التعاوني هو الافتقار العام للمحلول لحظر I / O. وفي Coroutines المزيفة، ستواجه أيضا المشكلة التي لا يمكنك تبديل "المواضيع" من أي شيء سوى المستوى العلوي من المكدس داخل مؤشر ترابط.

بعد أن قمت بإجراء تطبيق معقد قليلا مع Coloutines وهمية، ستبدأ حقا في تقدير العمل الذي ينتقل إلى جدولة عملية على مستوى نظام التشغيل.

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