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

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

في وحدة المعالجة المركزية ذات النوى 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
من التنفيذ.

في انتظار المواضيع لإنهاء التنفيذ
إذا كنت تريد 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 القادمة. ترميز سعيد!