خيوط بايثون: مقدمة

نشرت: 2022-10-25

في هذا البرنامج التعليمي ، ستتعلم كيفية استخدام وحدة الترابط المضمنة في Python لاستكشاف إمكانات تعدد مؤشرات الترابط في Python.

بدءًا من أساسيات العمليات والخيوط ، ستتعلم كيفية عمل تعدد مؤشرات الترابط في Python - مع فهم مفاهيم التزامن والتوازي. ستتعلم بعد ذلك كيفية بدء تشغيل واحد أو أكثر من الخيوط في Python باستخدام وحدة threading المدمجة.

هيا بنا نبدأ.

العمليات مقابل الخيوط: ما هي الاختلافات؟

ما هي العملية؟

العملية هي أي مثيل لبرنامج يحتاج إلى تشغيل.

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

خيوط وحدة المعالجة المركزية proc

فهم العمليات والخيوط

داخليًا ، تحتوي العملية على ذاكرة مخصصة تخزن الرمز والبيانات المقابلة للعملية.

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

كل خيط له مكدس وسجلات خاصة به ولكن ليس ذاكرة مخصصة. يمكن لجميع الخيوط المرتبطة بعملية الوصول إلى البيانات. لذلك ، تتم مشاركة البيانات والذاكرة بواسطة جميع مؤشرات ترابط العملية.

عملية وخيوط

في وحدة المعالجة المركزية ذات النوى N ، يمكن تنفيذ العمليات N بالتوازي في نفس الوقت. ومع ذلك ، لا يمكن أبدًا تنفيذ خيطين من نفس العملية بالتوازي - ولكن يمكن تنفيذهما بشكل متزامن. سنتناول مفهوم التزامن مقابل التوازي في القسم التالي.

بناءً على ما تعلمناه حتى الآن ، دعنا نلخص الاختلافات بين العملية والخيط.

ميزة معالجة خيط
ذاكرة ذاكرة مخصصة ذكريات مشتركه
طريقة التنفيذ متوازي ومتزامن منافس؛ لكن ليس بالتوازي
تم التعامل مع التنفيذ من قبل نظام التشغيل مترجم CPython

تعدد العمليات في بايثون

في Python ، يضمن Global Interpreter Lock (GIL) أن مؤشر ترابط واحد فقط يمكنه الحصول على القفل وتشغيله في أي وقت. يجب أن تحصل جميع مؤشرات الترابط على هذا القفل للتشغيل. يضمن ذلك إمكانية تنفيذ مؤشر ترابط واحد فقط - في أي نقطة زمنية محددة - وتجنب تعدد مؤشرات الترابط في وقت واحد .

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

إذن كيف نحقق إمكانات تعدد مؤشرات الترابط في بايثون؟ لفهم هذا ، دعونا نناقش مفهومي التزامن والتوازي.

التزامن مقابل التوازي: نظرة عامة

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

إذا كانت هناك أربع عمليات ، فيمكن لكل عملية تشغيلها بشكل مستقل وفي نفس الوقت على كل من النوى الأربعة. لنفترض أن كل عملية لها خيطين.

التوازي متعدد النواة

لفهم كيفية عمل خيوط المعالجة ، دعنا ننتقل من بنية المعالج متعدد النواة إلى بنية المعالج أحادي المركز. كما ذكرنا ، يمكن أن يكون مؤشر ترابط واحد فقط نشطًا في مثيل تنفيذ معين ؛ لكن نواة المعالج يمكنها التبديل بين الخيوط.

الشفرة

على سبيل المثال ، غالبًا ما تنتظر مؤشرات الترابط المرتبطة بالإدخال / الإخراج عمليات الإدخال / الإخراج: القراءة في إدخال المستخدم ، وقراءات قاعدة البيانات ، وعمليات الملفات. خلال فترة الانتظار هذه ، يمكنه تحرير القفل بحيث يمكن تشغيل الخيط الآخر. يمكن أن يكون وقت الانتظار أيضًا عملية بسيطة مثل النوم لمدة n ثانية.

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

إذا كنت ترغب في تنفيذ التوازي على مستوى العملية في تطبيقك ، ففكر في استخدام المعالجة المتعددة بدلاً من ذلك.

وحدة خيوط Python: الخطوات الأولى

يتم شحن Python مع وحدة threading التي يمكنك استيرادها إلى نص Python النصي.

 import threading

لإنشاء كائن مؤشر ترابط في Python ، يمكنك استخدام مُنشئ Thread : threading.Thread(...) . هذا هو بناء الجملة العام الذي يكفي لمعظم تطبيقات خيوط المعالجة:

 threading.Thread(target=...,args=...)

هنا،

  • target هو وسيطة الكلمة الأساسية التي تشير إلى لغة Python القابلة للاستدعاء
  • args هي مجموعة الوسائط التي يأخذها الهدف.

ستحتاج إلى Python 3.x لتشغيل أمثلة التعليمات البرمجية في هذا البرنامج التعليمي. قم بتنزيل الكود وتابعه.

كيفية تحديد وتشغيل المواضيع في بايثون

دعنا نحدد الخيط الذي يدير وظيفة الهدف.

الوظيفة الهدف هي some_func .

 import threading import time def some_func(): print("Running some_func...") time.sleep(2) print("Finished running some_func.") thread1 = threading.Thread(target=some_func) thread1.start() print(threading.active_count())

دعنا نحلل ما يفعله مقتطف الشفرة أعلاه:

  • تستورد وحدات threading time .
  • تحتوي الوظيفة some_func على عبارات print() وصفية وتتضمن عملية سكون لمدة ثانيتين: time.sleep(n) يتسبب في سكون الوظيفة لمدة n ثانية.
  • بعد ذلك ، نحدد موضوع thread_1 مع الهدف مثل some_func . threading.Thread(target=...) ينشئ كائن مؤشر ترابط.
  • ملاحظة : حدد اسم الوظيفة وليس استدعاء الوظيفة ؛ استخدم some_func وليس some_func() .
  • إنشاء كائن مؤشر ترابط لا يبدأ مؤشر ترابط؛ استدعاء طريقة start() على كائن مؤشر الترابط يفعل.
  • للحصول على عدد المواضيع النشطة ، نستخدم وظيفة active_count() .

يعمل سكربت Python على الخيط الرئيسي ، وننشئ خيطًا آخر ( thread1 ) لتشغيل الوظيفة some_func بحيث يكون عدد الخيط النشط اثنين ، كما هو موضح في الإخراج:

 # Output Running some_func... 2 Finished running some_func.

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

موضوع 1 السابقين

في انتظار المواضيع لإنهاء التنفيذ

إذا كنت تريد thread1 إنهاء التنفيذ ، يمكنك استدعاء طريقة join() عليه بعد بدء الخيط. سيؤدي القيام بذلك إلى انتظار thread1 لإنهاء التنفيذ دون التبديل إلى مؤشر الترابط الرئيسي.

 import threading import time def some_func(): print("Running some_func...") time.sleep(2) print("Finished running some_func.") thread1 = threading.Thread(target=some_func) thread1.start() thread1.join() print(threading.active_count())

الآن ، تم الانتهاء من تنفيذ thread1 قبل أن نطبع عدد الخيط النشط. لذلك يتم تشغيل الخيط الرئيسي فقط ، مما يعني أن عدد سلاسل الرسائل النشطة واحد.

 # Output Running some_func... Finished running some_func. 1

كيفية تشغيل خيوط متعددة في بايثون

بعد ذلك ، دعنا ننشئ خيطين لتشغيل وظيفتين مختلفتين.

هنا ، count_down هي دالة تأخذ رقمًا كوسيطة وتقوم بالعد التنازلي من هذا الرقم إلى الصفر.

 def count_down(n): for i in range(n,-1,-1): print(i)

نحدد count_up ، وهي دالة أخرى في Python يتم حسابها من صفر إلى رقم معين.

 def count_up(n): for i in range(n+1): print(i)

عند استخدام وظيفة range() مع range(start, stop, step) stop يتم استبعاد نقطة النهاية بشكل افتراضي.

- للعد التنازلي من رقم معين إلى صفر ، يمكنك استخدام قيمة step السالبة -1 وتعيين قيمة stop على -1 بحيث يتم تضمين الصفر.

- وبالمثل ، للعد حتى n ، يجب عليك ضبط قيمة stop على n + 1 . نظرًا لأن القيم الافتراضية start step هي 0 و 1 ، على التوالي ، يمكنك استخدام range(n + 1) للحصول على التسلسل من 0 إلى n.

بعد ذلك ، نحدد خيطين ، thread1 و thread2 لتشغيل وظائف count_down و count_up ، على التوالي. نضيف بيانات print وعمليات sleep لكلتا الوظيفتين.

عند إنشاء كائنات مؤشر الترابط ، لاحظ أنه يجب تحديد وسيطات الدالة الهدف على أنها مجموعة - إلى المعلمة args . نظرًا لأن كل من الدالتين ( count_down و count_up ) تأخذ في وسيطة واحدة. سيتعين عليك إدخال فاصلة بوضوح بعد القيمة. هذا يضمن استمرار تمرير الوسيطة كصفوفة tuple ، حيث يتم استنتاج العناصر التالية على أنها None .

 import threading import time def count_down(n): for i in range(n,-1,-1): print("Running thread1....") print(i) time.sleep(1) def count_up(n): for i in range(n+1): print("Running thread2...") print(i) time.sleep(1) thread1 = threading.Thread(target=count_down,args=(10,)) thread2 = threading.Thread(target=count_up,args=(5,)) thread1.start() thread2.start()

في الإخراج:

  • تعمل الدالة count_up على thread2 حتى 5 بدءًا من 0.
  • تعمل وظيفة count_down على thread1 بالعد التنازلي من 10 إلى 0.
 # Output Running thread1.... 10 Running thread2... 0 Running thread1.... 9 Running thread2... 1 Running thread1.... 8 Running thread2... 2 Running thread1.... 7 Running thread2... 3 Running thread1.... 6 Running thread2... 4 Running thread1.... 5 Running thread2... 5 Running thread1.... 4 Running thread1.... 3 Running thread1.... 2 Running thread1.... 1 Running thread1.... 0

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

تلخيص لما سبق

في هذا البرنامج التعليمي ، تعلمت كيفية استخدام وحدة الترابط المضمنة في Python لتنفيذ تعدد مؤشرات الترابط. فيما يلي ملخص للوجبات الرئيسية:

  • يمكن استخدام مُنشئ مؤشر الترابط لإنشاء كائن مؤشر ترابط. باستخدام threading.Thread (target = <callable>، args = (<tuple of args>)) ينشئ مؤشر ترابط يقوم بتشغيل الهدف القابل للاستدعاء مع الوسائط المحددة في args .
  • يعمل برنامج Python على خيط رئيسي ، لذا فإن كائنات الخيط التي تقوم بإنشائها هي خيوط إضافية. يمكنك استدعاء active_count () الدالة بإرجاع عدد المواضيع النشطة في أي حالة.
  • يمكنك بدء خيط باستخدام طريقة start () على كائن مؤشر الترابط والانتظار حتى ينتهي التنفيذ باستخدام طريقة Join () .

يمكنك ترميز أمثلة إضافية عن طريق تعديل أوقات الانتظار ومحاولة إجراء عملية إدخال / إخراج مختلفة وغير ذلك. تأكد من تنفيذ multithreading في مشاريع Python القادمة. ترميز سعيد!