ما هو "التخزين المحلي للخيط" في بايثون ولماذا أحتاجه؟
-
01-07-2019 - |
سؤال
في لغة بايثون على وجه التحديد، كيف تتم مشاركة المتغيرات بين سلاسل الرسائل؟
على الرغم من أنني استخدمت threading.Thread
قبل أن لم أفهم حقًا أو رأيت أمثلة لكيفية مشاركة المتغيرات.هل هي مشتركة بين الخيط الرئيسي والأطفال أم بين الأطفال فقط؟متى سأحتاج إلى استخدام التخزين المحلي لسلسلة المحادثات لتجنب هذه المشاركة؟
لقد رأيت العديد من التحذيرات حول مزامنة الوصول إلى البيانات المشتركة بين سلاسل الرسائل باستخدام الأقفال ولكني لم أر حتى الآن مثالاً جيدًا للمشكلة.
شكرا لك مقدما!
المحلول
في بايثون، تتم مشاركة كل شيء، باستثناء المتغيرات الوظيفية المحلية (لأن كل استدعاء دالة يحصل على مجموعته الخاصة من المتغيرات المحلية، وتكون الخيوط دائمًا استدعاءات دالة منفصلة.) وحتى ذلك الحين، فقط المتغيرات نفسها (الأسماء التي تشير إلى الكائنات) محلية للوظيفة؛فالأشياء في حد ذاتها دائمًا ما تكون عالمية، وأي شيء يمكن أن يشير إليها.ال Thread
كائن لمؤشر ترابط معين ليس كائنًا خاصًا في هذا الصدد.إذا قمت بتخزين Thread
كائن في مكان ما يمكن لجميع سلاسل الرسائل الوصول إليه (مثل متغير عام) ثم يمكن لجميع سلاسل الرسائل الوصول إلى ذلك المتغير Thread
هدف.إذا كنت تريد تعديل ذريًا أي شئ أنك لم تقم بإنشائه في هذا الموضوع نفسه، ولم تقم بتخزينه في أي مكان يمكن أن يصل إليه مؤشر ترابط آخر، فيجب عليك حمايته بقفل.ويجب بالطبع أن تشترك جميع الخيوط في نفس القفل، وإلا فلن يكون فعالًا جدًا.
إذا كنت تريد تخزينًا محليًا لمؤشر الترابط الفعلي، فهذا هو المكان threading.local
ادخل.سمات threading.local
لا يتم مشاركتها بين المواضيع.يرى كل مؤشر ترابط فقط السمات التي وضعها هناك.إذا كنت مهتمًا بتنفيذه، فالمصدر موجود _threading_local.py في المكتبة القياسية.
نصائح أخرى
خذ بعين الاعتبار الكود التالي:
#/usr/bin/env python
from time import sleep
from random import random
from threading import Thread, local
data = local()
def bar():
print("I'm called from", data.v)
def foo():
bar()
class T(Thread):
def run(self):
sleep(random())
data.v = self.getName() # Thread-1 and Thread-2 accordingly
sleep(1)
foo()
>> T().start(); T().start() I'm called from Thread-2 I'm called from Thread-1
هنا يتم استخدام threading.local() كطريقة سريعة وقذرة لتمرير بعض البيانات من run() إلى bar() دون تغيير واجهة foo().
لاحظ أن استخدام المتغيرات العامة لن يفي بالغرض:
#/usr/bin/env python
from time import sleep
from random import random
from threading import Thread
def bar():
global v
print("I'm called from", v)
def foo():
bar()
class T(Thread):
def run(self):
global v
sleep(random())
v = self.getName() # Thread-1 and Thread-2 accordingly
sleep(1)
foo()
>> T().start(); T().start() I'm called from Thread-2 I'm called from Thread-2
وفي الوقت نفسه، إذا كان بإمكانك تمرير هذه البيانات كوسيطة للتابع foo() - فستكون طريقة أكثر أناقة وتصميمًا جيدًا:
from threading import Thread
def bar(v):
print("I'm called from", v)
def foo(v):
bar(v)
class T(Thread):
def run(self):
foo(self.getName())
ولكن هذا ليس ممكنًا دائمًا عند استخدام تعليمات برمجية تابعة لجهة خارجية أو سيئة التصميم.
يمكنك إنشاء تخزين محلي لمؤشر الترابط باستخدام threading.local()
.
>>> tls = threading.local()
>>> tls.x = 4
>>> tls.x
4
ستكون البيانات المخزنة في TLS فريدة لكل موضوع مما سيساعد على ضمان عدم حدوث مشاركة غير مقصودة.
كما هو الحال في أي لغة أخرى، كل موضوع في بايثون لديه حق الوصول إلى نفس المتغيرات.لا يوجد فرق بين "الخيط الرئيسي" والخيوط الفرعية.
أحد الاختلافات مع Python هو أن Global Interpreter Lock يعني أن مؤشر ترابط واحد فقط يمكنه تشغيل كود Python في المرة الواحدة.هذا لا يساعد كثيرًا عندما يتعلق الأمر بمزامنة الوصول، حيث لا تزال جميع مشكلات الاستباقية المعتادة مطبقة، ويجب عليك استخدام أساسيات الترابط تمامًا كما هو الحال في اللغات الأخرى.هذا يعني أنك بحاجة إلى إعادة النظر فيما إذا كنت تستخدم مؤشرات الترابط للأداء.