முக்கிய பைதான் கன்கரன்சி பேட்டர்ன்ஸை ஆராய்ந்து த்ரெட்-சேஃப் தரவு கட்டமைப்புகளை செயல்படுத்த கற்று, உலகளாவிய பயன்பாடுகளுக்கு வலுவான மற்றும் அளவிடக்கூடிய செயலிகளை உருவாக்குங்கள்.
பைதான் கன்கரன்சி பேட்டர்ன்ஸ்: உலகளாவிய பயன்பாடுகளுக்கான த்ரெட்-சேஃப் தரவு கட்டமைப்புகளில் தேர்ச்சி பெறுதல்
இன்றைய இணைக்கப்பட்ட உலகில், மென்பொருள் பயன்பாடுகள் பெரும்பாலும் ஒரே நேரத்தில் பல பணிகளைக் கையாள வேண்டும், சுமையின் கீழ் பதிலளிக்கக்கூடியதாக இருக்க வேண்டும், மேலும் பரந்த அளவிலான தரவை திறமையாக செயலாக்க வேண்டும். நிகழ்நேர நிதி வர்த்தக தளங்கள் மற்றும் உலகளாவிய இ-காமர்ஸ் அமைப்புகள் முதல் சிக்கலான அறிவியல் உருவகப்படுத்துதல்கள் மற்றும் தரவு செயலாக்க குழாய்கள் வரை, உயர் செயல்திறன் மற்றும் அளவிடக்கூடிய தீர்வுகளுக்கான தேவை உலகளாவியது. பைதான், அதன் பன்முகத்தன்மை மற்றும் விரிவான நூலகங்களுடன், அத்தகைய அமைப்புகளை உருவாக்குவதற்கான ஒரு சக்திவாய்ந்த தேர்வாகும். இருப்பினும், பைத்தானின் முழுமையான கன்கரன்ட் திறனைத் திறக்க, குறிப்பாக பகிரப்பட்ட வளங்களைக் கையாளும் போது, கன்கரன்சி பேட்டர்ன்ஸ் பற்றிய ஆழமான புரிதல் மற்றும், முக்கியமாக, த்ரெட்-சேஃப் தரவு கட்டமைப்புகளை எவ்வாறு செயல்படுத்துவது என்பது தேவைப்படுகிறது. இந்த விரிவான வழிகாட்டி பைத்தானின் த்ரெடிங் மாதிரியின் நுணுக்கங்களுக்கு வழிகாட்டும், பாதுகாப்பற்ற கன்கரன்ட் அணுகலின் ஆபத்துக்களை விளக்கும், மேலும் த்ரெட்-சேஃப் தரவு கட்டமைப்புகளில் தேர்ச்சி பெறுவதன் மூலம் வலுவான, நம்பகமான மற்றும் உலகளவில் அளவிடக்கூடிய பயன்பாடுகளை உருவாக்க உங்களுக்கு அறிவை வழங்கும். நாங்கள் பல்வேறு ஒத்திசைவு பிரிமிட்டிவ்கள் மற்றும் நடைமுறை செயல்படுத்தும் நுட்பங்களை ஆராய்வோம், உங்கள் பைதான் பயன்பாடுகள் தரவு நேர்மை அல்லது செயல்திறனை சமரசம் செய்யாமல் கண்டங்கள் மற்றும் நேர மண்டலங்களில் பயனர்கள் மற்றும் அமைப்புகளுக்கு சேவை செய்யும் ஒரு கன்கரன்ட் சூழலில் நம்பிக்கையுடன் செயல்பட முடியும் என்பதை உறுதி செய்வோம்.
பைத்தானில் கன்கரன்சியைப் புரிந்துகொள்வது: ஒரு உலகளாவிய கண்ணோட்டம்
கன்கரன்சி என்பது ஒரு நிரலின் வெவ்வேறு பகுதிகள் அல்லது பல நிரல்கள், சுதந்திரமாக மற்றும் இணையாக இயங்குவது போன்ற தோற்றத்தை அளிக்கும் திறன் ஆகும். இது ஒரு நிரலை கட்டமைக்கும் ஒரு வழியாகும், இது ஒரே நேரத்தில் பல செயல்பாடுகளை முன்னேற அனுமதிக்கிறது, அடிப்படை அமைப்பு ஒரு குறிப்பிட்ட நேரத்தில் ஒரு செயல்பாட்டை மட்டுமே செயல்படுத்த முடியும் என்றாலும். இது பேரலலிசம் என்பதிலிருந்து வேறுபட்டது, இது பல CPU கோர்களில் பொதுவாக பல செயல்பாடுகளை ஒரே நேரத்தில் செயல்படுத்துவதை உள்ளடக்கியது. உலகளவில் பயன்படுத்தப்படும் பயன்பாடுகளுக்கு, பதிலளிக்கக்கூடிய தன்மையைப் பராமரிக்கவும், ஒரே நேரத்தில் பல வாடிக்கையாளர் கோரிக்கைகளைக் கையாளவும், மற்றும் வாடிக்கையாளர்கள் அல்லது தரவு மூலங்கள் எங்கு அமைந்திருந்தாலும் I/O செயல்பாடுகளை திறமையாக நிர்வகிக்கவும் கன்கரன்சி மிக முக்கியமானது.
பைத்தானின் குளோபல் இன்டர்பிரிட்டர் லாக் (GIL) மற்றும் அதன் தாக்கங்கள்
பைதான் கன்கரன்சியில் ஒரு அடிப்படைக் கருத்து குளோபல் இன்டர்பிரிட்டர் லாக் (GIL) ஆகும். GIL என்பது பைதான் ஆப்ஜெக்ட்களுக்கான அணுகலைப் பாதுகாக்கும் ஒரு மியூடெக்ஸ் ஆகும், இது பல நேட்டிவ் த்ரெட்கள் ஒரே நேரத்தில் பைதான் பைட் கோடுகளை இயக்குவதைத் தடுக்கிறது. இதன் பொருள், ஒரு மல்டி-கோர் செயலியிலும் கூட, ஒரு நேரத்தில் ஒரு த்ரெட் மட்டுமே பைதான் பைட் கோடை இயக்க முடியும். இந்த வடிவமைப்புத் தேர்வு பைத்தானின் மெமரி மேலாண்மை மற்றும் குப்பை சேகரிப்பை எளிதாக்குகிறது, ஆனால் பைத்தானின் மல்டித்ரெடிங் திறன்கள் குறித்த தவறான புரிதல்களுக்கு அடிக்கடி வழிவகுக்கிறது.
GIL ஒரு பைதான் செயல்முறைக்குள் உண்மையான CPU-பவுண்ட் பேரலலிசத்தைத் தடுத்தாலும், அது மல்டித்ரெடிங்கின் நன்மைகளை முழுமையாக மறுக்காது. I/O செயல்பாடுகளின் போது (எ.கா., ஒரு நெட்வொர்க் சாக்கெட்டிலிருந்து படித்தல், ஒரு கோப்பில் எழுதுதல், தரவுத்தள வினவல்கள்) அல்லது சில வெளிப்புற C நூலகங்களை அழைக்கும்போது GIL வெளியிடப்படுகிறது. இந்த முக்கியமான விவரம் பைதான் த்ரெட்களை I/O-பவுண்ட் பணிகளுக்கு நம்பமுடியாத அளவிற்கு பயனுள்ளதாக ஆக்குகிறது. எடுத்துக்காட்டாக, வெவ்வேறு நாடுகளில் உள்ள பயனர்களிடமிருந்து வரும் கோரிக்கைகளைக் கையாளும் ஒரு வலை சேவையகம் த்ரெட்களைப் பயன்படுத்தி இணைப்புகளை ஒரே நேரத்தில் நிர்வகிக்கலாம், ஒரு வாடிக்கையாளரிடமிருந்து தரவுக்காகக் காத்திருக்கும்போது மற்றொரு வாடிக்கையாளரின் கோரிக்கையைச் செயலாக்கலாம், ஏனெனில் காத்திருப்பில் பெரும்பகுதி I/O ஐ உள்ளடக்கியது. இதேபோல், விநியோகிக்கப்பட்ட APIகளிலிருந்து தரவைப் பெறுவது அல்லது பல்வேறு உலகளாவிய மூலங்களிலிருந்து தரவு ஸ்ட்ரீம்களைச் செயலாக்குவது GIL இருந்தாலும், த்ரெட்களைப் பயன்படுத்துவதன் மூலம் கணிசமாக வேகப்படுத்தப்படலாம். முக்கிய விஷயம் என்னவென்றால், ஒரு த்ரெட் I/O செயல்பாடு முடிவடையும் வரை காத்திருக்கும்போது, மற்ற த்ரெட்கள் GIL-ஐப் பெற்று பைதான் பைட் கோடை இயக்க முடியும். த்ரெட்கள் இல்லாமல், இந்த I/O செயல்பாடுகள் முழு பயன்பாட்டையும் தடுத்துவிடும், இது மந்தமான செயல்திறன் மற்றும் மோசமான பயனர் அனுபவத்திற்கு வழிவகுக்கும், குறிப்பாக உலகளவில் விநியோகிக்கப்பட்ட சேவைகளுக்கு, நெட்வொர்க் தாமதம் ஒரு குறிப்பிடத்தக்க காரணியாக இருக்கலாம்.
எனவே, GIL இருந்தபோதிலும், த்ரெட்-பாதுகாப்பு மிக முக்கியமானது. ஒரு நேரத்தில் ஒரு த்ரெட் மட்டுமே பைதான் பைட் கோடை இயக்கினாலும், த்ரெட்களின் இடைப்பட்ட இயக்கம் பல த்ரெட்கள் பகிரப்பட்ட தரவுக் கட்டமைப்புகளை அணுக்கருவல்லாத முறையில் அணுகவும் மாற்றவும் முடியும் என்பதைக் குறிக்கிறது. இந்த மாற்றங்கள் சரியாக ஒத்திசைக்கப்படாவிட்டால், ரேஸ் கண்டிஷன்கள் ஏற்படலாம், இது தரவு சிதைவு, கணிக்க முடியாத நடத்தை மற்றும் பயன்பாட்டு செயலிழப்புகளுக்கு வழிவகுக்கும். இது நிதி அமைப்புகள், உலகளாவிய விநியோகச் சங்கிலிகளுக்கான சரக்கு மேலாண்மை அல்லது நோயாளி பதிவு அமைப்புகள் போன்ற தரவு நேர்மை பேரம் பேச முடியாத அமைப்புகளில் குறிப்பாக முக்கியமானது. GIL மல்டித்ரெடிங்கின் கவனத்தை CPU பேரலலிசத்திலிருந்து I/O கன்கரன்சிக்கு மாற்றுகிறது, ஆனால் வலுவான தரவு ஒத்திசைவு பேட்டர்ன்களுக்கான தேவை தொடர்கிறது.
பாதுகாப்பற்ற கன்கரன்ட் அணுகலின் ஆபத்துகள்: ரேஸ் கண்டிஷன்கள் மற்றும் தரவு சிதைவு
பல த்ரெட்கள் சரியான ஒத்திசைவு இல்லாமல் ஒரே நேரத்தில் பகிரப்பட்ட தரவை அணுகி மாற்றும்போது, செயல்பாடுகளின் சரியான வரிசை தீர்மானிக்க முடியாததாகிவிடும். இந்த தீர்மானிக்க முடியாத தன்மை ஒரு ரேஸ் கண்டிஷன் எனப்படும் பொதுவான மற்றும் நயவஞ்சகமான பிழைக்கு வழிவகுக்கும். ஒரு செயல்பாட்டின் விளைவு மற்ற கட்டுப்பாடற்ற நிகழ்வுகளின் வரிசை அல்லது நேரத்தைப் பொறுத்து இருக்கும்போது ஒரு ரேஸ் கண்டிஷன் ஏற்படுகிறது. மல்டித்ரெடிங்கின் சூழலில், பகிரப்பட்ட தரவின் இறுதி நிலை இயக்க முறைமை அல்லது பைதான் இன்டர்பிரிட்டரால் த்ரெட்களின் தன்னிச்சையான திட்டமிடலைப் பொறுத்தது.
ரேஸ் கண்டிஷன்களின் விளைவு பெரும்பாலும் தரவு சிதைவு ஆகும். இரண்டு த்ரெட்கள் ஒரு பகிரப்பட்ட கவுண்டர் மாறியை அதிகரிக்க முயற்சிக்கும் ஒரு காட்சியை கற்பனை செய்து பாருங்கள். ஒவ்வொரு த்ரெட்டும் மூன்று தர்க்கரீதியான படிகளைச் செய்கிறது: 1) தற்போதைய மதிப்பைப் படித்தல், 2) மதிப்பை அதிகரித்தல், மற்றும் 3) புதிய மதிப்பை மீண்டும் எழுதுதல். இந்த படிகள் ஒரு துரதிர்ஷ்டவசமான வரிசையில் இடைப்பட்டால், அதிகரிப்புகளில் ஒன்று இழக்கப்படலாம். எடுத்துக்காட்டாக, த்ரெட் A மதிப்பைப் படித்தால் (சொல்லுங்கள், 0), பின்னர் த்ரெட் B அதே மதிப்பைப் படித்தால் (0), த்ரெட் A அதன் அதிகரித்த மதிப்பை (1) எழுதுவதற்கு முன்பு, பின்னர் த்ரெட் B அதன் படித்த மதிப்பை (1 ஆக) அதிகரித்து அதை மீண்டும் எழுதுகிறது, இறுதியாக த்ரெட் A அதன் அதிகரித்த மதிப்பை (1) எழுதுகிறது, கவுண்டர் எதிர்பார்த்த 2 க்கு பதிலாக 1 ஆக மட்டுமே இருக்கும். இந்த வகையான பிழையை சரிசெய்வது மிகவும் கடினம், ஏனெனில் இது த்ரெட் செயல்பாட்டின் சரியான நேரத்தைப் பொறுத்து எப்போதும் வெளிப்படாது. ஒரு உலகளாவிய பயன்பாட்டில், அத்தகைய தரவு சிதைவு தவறான நிதி பரிவர்த்தனைகள், வெவ்வேறு பிராந்தியங்களில் சீரற்ற சரக்கு நிலைகள் அல்லது முக்கியமான அமைப்பு தோல்விகளுக்கு வழிவகுக்கும், இது நம்பிக்கையை சிதைத்து குறிப்பிடத்தக்க செயல்பாட்டு சேதத்தை ஏற்படுத்தும்.
குறியீடு எடுத்துக்காட்டு 1: ஒரு எளிய த்ரெட்-பாதுகாப்பற்ற கவுண்டர்
import threading
import time
class UnsafeCounter:
def __init__(self):
self.value = 0
def increment(self):
# Simulate some work
time.sleep(0.0001)
self.value += 1
def worker(counter, num_iterations):
for _ in range(num_iterations):
counter.increment()
if __name__ == "__main__":
counter = UnsafeCounter()
num_threads = 10
iterations_per_thread = 100000
threads = []
for _ in range(num_threads):
thread = threading.Thread(target=worker, args=(counter, iterations_per_thread))
threads.append(thread)
thread.start()
for thread in threads:
thread.join()
expected_value = num_threads * iterations_per_thread
print(f"Expected value: {expected_value}")
print(f"Actual value: {counter.value}")
if counter.value != expected_value:
print("WARNING: Race condition detected! Actual value is less than expected.")
else:
print("No race condition detected in this run (unlikely for many threads).")
இந்த எடுத்துக்காட்டில், UnsafeCounter இன் increment முறை ஒரு முக்கியமான பகுதியாகும்: இது self.value ஐ அணுகி மாற்றுகிறது. பல worker த்ரெட்கள் ஒரே நேரத்தில் increment ஐ அழைக்கும்போது, self.value க்கான படித்தல் மற்றும் எழுதுதல் இடைப்படலாம், இதனால் சில அதிகரிப்புகள் இழக்கப்படுகின்றன. num_threads மற்றும் iterations_per_thread போதுமான அளவு பெரியதாக இருக்கும்போது "உண்மையான மதிப்பு" கிட்டத்தட்ட எப்போதும் "எதிர்பார்க்கப்பட்ட மதிப்பை" விட குறைவாக இருப்பதைக் காண்பீர்கள், இது ஒரு ரேஸ் கண்டிஷன் காரணமாக தரவு சிதைவை தெளிவாக நிரூபிக்கிறது. இந்த கணிக்க முடியாத நடத்தை தரவு நிலைத்தன்மை தேவைப்படும் எந்தவொரு பயன்பாட்டிற்கும் ஏற்றுக்கொள்ள முடியாதது, குறிப்பாக உலகளாவிய பரிவர்த்தனைகள் அல்லது முக்கியமான பயனர் தரவை நிர்வகிக்கும் பயன்பாடுகளுக்கு.
பைத்தானில் உள்ள முக்கிய ஒத்திசைவு பிரிமிட்டிவ்கள்
ரேஸ் கண்டிஷன்களைத் தடுக்கவும், கன்கரன்ட் பயன்பாடுகளில் தரவு நேர்மையை உறுதி செய்யவும், பைத்தானின் threading தொகுதி ஒத்திசைவு பிரிமிட்டிவ்களின் தொகுப்பை வழங்குகிறது. இந்த கருவிகள் டெவலப்பர்கள் பகிரப்பட்ட வளங்களுக்கான அணுகலை ஒருங்கிணைக்க அனுமதிக்கின்றன, த்ரெட்கள் குறியீடு அல்லது தரவின் முக்கியமான பிரிவுகளுடன் எப்போது, எப்படி தொடர்பு கொள்ளலாம் என்பதைக் கட்டளையிடும் விதிகளை அமல்படுத்துகின்றன. சரியான பிரிமிட்டிவைத் தேர்ந்தெடுப்பது கையிலுள்ள குறிப்பிட்ட ஒத்திசைவு சவாலைப் பொறுத்தது.
லாக்குகள் (Mutexes)
ஒரு Lock (பெரும்பாலும் மியூடெக்ஸ் என குறிப்பிடப்படுகிறது, இது பரஸ்பர விலக்கலுக்கான சுருக்கம்) என்பது மிகவும் அடிப்படை மற்றும் பரவலாகப் பயன்படுத்தப்படும் ஒத்திசைவு பிரிமிட்டிவ் ஆகும். இது ஒரு பகிரப்பட்ட வளம் அல்லது குறியீட்டின் ஒரு முக்கியமான பகுதிக்கு அணுகலைக் கட்டுப்படுத்த ஒரு எளிய வழிமுறையாகும். ஒரு லாக்கிற்கு இரண்டு நிலைகள் உள்ளன: locked மற்றும் unlocked. பூட்டப்பட்ட லாக்கைப் பெற முயற்சிக்கும் எந்த த்ரெட்டும், தற்போது அதை வைத்திருக்கும் த்ரெட்டால் லாக் வெளியிடப்படும் வரை தடுக்கப்படும். இது ஒரு குறிப்பிட்ட நேரத்தில் ஒரு த்ரெட் மட்டுமே ஒரு குறிப்பிட்ட குறியீட்டுப் பகுதியை இயக்க முடியும் அல்லது ஒரு குறிப்பிட்ட தரவுக் கட்டமைப்பை அணுக முடியும் என்பதை உறுதி செய்கிறது, இதன் மூலம் ரேஸ் கண்டிஷன்களைத் தடுக்கிறது.
நீங்கள் ஒரு பகிரப்பட்ட வளத்திற்கு பிரத்யேக அணுகலை உறுதி செய்ய வேண்டியிருக்கும் போது லாக்குகள் சிறந்தவை. உதாரணமாக, ஒரு தரவுத்தள பதிவைப் புதுப்பித்தல், ஒரு பகிரப்பட்ட பட்டியலை மாற்றுதல் அல்லது பல த்ரெட்களிலிருந்து ஒரு பதிவு கோப்பில் எழுதுதல் ஆகியவை ஒரு லாக் அவசியமான காட்சிகளாகும்.
குறியீடு எடுத்துக்காட்டு 2: கவுண்டர் சிக்கலை சரிசெய்ய threading.Lock ஐப் பயன்படுத்துதல்
import threading
import time
class SafeCounter:
def __init__(self):
self.value = 0
self.lock = threading.Lock() # Initialize a lock
def increment(self):
with self.lock: # Acquire the lock before entering critical section
# Simulate some work
time.sleep(0.0001)
self.value += 1
# Lock is automatically released when exiting the 'with' block
def worker_safe(counter, num_iterations):
for _ in range(num_iterations):
counter.increment()
if __name__ == "__main__":
safe_counter = SafeCounter()
num_threads = 10
iterations_per_thread = 100000
threads = []
for _ in range(num_threads):
thread = threading.Thread(target=worker_safe, args=(safe_counter, iterations_per_thread))
threads.append(thread)
thread.start()
for thread in threads:
thread.join()
expected_value = num_threads * iterations_per_thread
print(f"Expected value: {expected_value}")
print(f"Actual value: {safe_counter.value}")
if safe_counter.value == expected_value:
print("SUCCESS: Counter is thread-safe!")
else:
print("ERROR: Race condition still present!")
இந்த சுத்திகரிக்கப்பட்ட SafeCounter எடுத்துக்காட்டில், நாங்கள் self.lock = threading.Lock() ஐ அறிமுகப்படுத்துகிறோம். increment முறை இப்போது with self.lock: அறிக்கையைப் பயன்படுத்துகிறது. இந்த சூழல் மேலாளர் self.value அணுகப்படுவதற்கு முன்பு லாக் பெறப்படுவதையும், ஒரு விதிவிலக்கு ஏற்பட்டாலும் கூட, பின்னர் தானாகவே வெளியிடப்படுவதையும் உறுதி செய்கிறது. இந்த செயலாக்கத்துடன், "உண்மையான மதிப்பு" "எதிர்பார்க்கப்பட்ட மதிப்புடன்" நம்பகத்தன்மையுடன் பொருந்தும், ரேஸ் கண்டிஷனின் வெற்றிகரமான தடுப்பை நிரூபிக்கிறது.
Lock இன் ஒரு மாறுபாடு RLock (மீண்டும் நுழையக்கூடிய லாக்) ஆகும். ஒரு RLock ஐ ஒரே த்ரெட்டால் பல முறை பெற முடியும், இது ஒரு டெட்லாக்கை ஏற்படுத்தாது. ஒரு த்ரெட் ஒரே லாக்கை பல முறை பெற வேண்டியிருக்கும் போது இது பயனுள்ளதாக இருக்கும், ஒருவேளை ஒரு ஒத்திசைக்கப்பட்ட முறை மற்றொரு ஒத்திசைக்கப்பட்ட முறையை அழைப்பதால். அத்தகைய ஒரு காட்சியில் ஒரு நிலையான Lock பயன்படுத்தப்பட்டால், த்ரெட் இரண்டாவது முறையாக லாக்கைப் பெற முயற்சிக்கும்போது தன்னைத்தானே டெட்லாக் செய்து கொள்ளும். RLock ஒரு "சுழற்சி அளவை" பராமரிக்கிறது மற்றும் அதன் சுழற்சி அளவு பூஜ்ஜியமாகக் குறையும் போது மட்டுமே லாக்கை வெளியிடுகிறது.
செமாஃபோர்கள்
ஒரு Semaphore என்பது லாக்கின் மிகவும் பொதுவான பதிப்பாகும், இது ஒரு குறிப்பிட்ட எண்ணிக்கையிலான "ஸ்லாட்களை" கொண்ட ஒரு வளத்திற்கான அணுகலைக் கட்டுப்படுத்த வடிவமைக்கப்பட்டுள்ளது. பிரத்யேக அணுகலை வழங்குவதற்குப் பதிலாக (ஒரு லாக் போல, இது அடிப்படையில் 1 மதிப்புள்ள ஒரு செமாஃபோர்), ஒரு செமாஃபோர் ஒரு குறிப்பிட்ட எண்ணிக்கையிலான த்ரெட்களை ஒரே நேரத்தில் ஒரு வளத்தை அணுக அனுமதிக்கிறது. இது ஒரு உள் கவுண்டரைப் பராமரிக்கிறது, இது ஒவ்வொரு acquire() அழைப்பால் குறைக்கப்படுகிறது மற்றும் ஒவ்வொரு release() அழைப்பால் அதிகரிக்கப்படுகிறது. ஒரு த்ரெட் ஒரு செமாஃபோரைப் பெற முயற்சிக்கும்போது அதன் கவுண்டர் பூஜ்ஜியமாக இருந்தால், மற்றொரு த்ரெட் அதை வெளியிடும் வரை அது தடுக்கப்படும்.
செமாஃபோர்கள் வளக் குளங்களை நிர்வகிப்பதற்கு குறிப்பாக பயனுள்ளதாக இருக்கும், அதாவது ஒரு குறிப்பிட்ட எண்ணிக்கையிலான தரவுத்தள இணைப்புகள், நெட்வொர்க் சாக்கெட்டுகள் அல்லது ஒரு உலகளாவிய சேவை கட்டமைப்பில் கணக்கீட்டு அலகுகள், அங்கு வளக் கிடைப்பானது செலவு அல்லது செயல்திறன் காரணங்களுக்காக வரம்பிடப்படலாம். எடுத்துக்காட்டாக, உங்கள் பயன்பாடு ஒரு மூன்றாம் தரப்பு API உடன் தொடர்பு கொண்டால், அது ஒரு வீத வரம்பை விதிக்கிறது (எ.கா., ஒரு குறிப்பிட்ட IP முகவரியிலிருந்து வினாடிக்கு 10 கோரிக்கைகள் மட்டுமே), ஒரே நேரத்தில் API அழைப்புகளின் எண்ணிக்கையைக் கட்டுப்படுத்துவதன் மூலம் உங்கள் பயன்பாடு இந்த வரம்பை மீறவில்லை என்பதை உறுதிப்படுத்த ஒரு செமாஃபோர் பயன்படுத்தப்படலாம்.
குறியீடு எடுத்துக்காட்டு 3: threading.Semaphore உடன் கன்கரன்ட் அணுகலைக் கட்டுப்படுத்துதல்
import threading
import time
import random
def database_connection_simulator(thread_id, semaphore):
print(f"Thread {thread_id}: Waiting to acquire DB connection...")
with semaphore: # Acquire a slot in the connection pool
print(f"Thread {thread_id}: Acquired DB connection. Performing query...")
# Simulate database operation
time.sleep(random.uniform(0.5, 2.0))
print(f"Thread {thread_id}: Finished query. Releasing DB connection.")
# Lock is automatically released when exiting the 'with' block
if __name__ == "__main__":
max_connections = 3 # Only 3 concurrent database connections allowed
db_semaphore = threading.Semaphore(max_connections)
num_threads = 10
threads = []
for i in range(num_threads):
thread = threading.Thread(target=database_connection_simulator, args=(i, db_semaphore))
threads.append(thread)
thread.start()
for thread in threads:
thread.join()
print("All threads finished their database operations.")
இந்த எடுத்துக்காட்டில், db_semaphore 3 மதிப்புடன் தொடங்கப்பட்டுள்ளது, அதாவது ஒரே நேரத்தில் மூன்று த்ரெட்கள் மட்டுமே "பெறப்பட்ட தரவுத்தள இணைப்பு" நிலையில் இருக்க முடியும். வெளியீடு த்ரெட்கள் காத்திருப்பதையும், மூன்று பேர் கொண்ட குழுக்களாக முன்னேறுவதையும் தெளிவாகக் காண்பிக்கும், இது கன்கரன்ட் வள அணுகலின் திறமையான வரம்பை நிரூபிக்கிறது. இந்த பேட்டர்ன் பெரிய அளவிலான, விநியோகிக்கப்பட்ட அமைப்புகளில் வரையறுக்கப்பட்ட வளங்களை நிர்வகிப்பதற்கு முக்கியமானது, அங்கு அதிகப்படியான பயன்பாடு செயல்திறன் குறைவு அல்லது சேவை மறுப்புக்கு வழிவகுக்கும்.
நிகழ்வுகள் (Events)
ஒரு Event என்பது ஒரு எளிய ஒத்திசைவு ஆப்ஜெக்ட் ஆகும், இது ஒரு த்ரெட் மற்ற த்ரெட்களுக்கு ஒரு நிகழ்வு ஏற்பட்டுள்ளது என்று சமிக்ஞை செய்ய அனுமதிக்கிறது. ஒரு Event ஆப்ஜெக்ட் ஒரு உள் கொடியைப் பராமரிக்கிறது, அதை True அல்லது False என அமைக்கலாம். த்ரெட்கள் கொடி True ஆகும் வரை காத்திருக்கலாம், அது ஆகும் வரை தடுக்கப்பட்டு, மற்றொரு த்ரெட் கொடியை அமைக்கலாம் அல்லது அழிக்கலாம்.
ஒரு தயாரிப்பாளர் த்ரெட் ஒரு நுகர்வோர் த்ரெட்டிற்கு தரவு தயாராக உள்ளது என்று சமிக்ஞை செய்ய வேண்டிய எளிய தயாரிப்பாளர்-நுகர்வோர் காட்சிகளுக்கு அல்லது பல கூறுகளுக்கு இடையே தொடக்க/நிறுத்த வரிசைகளை ஒருங்கிணைப்பதற்கு நிகழ்வுகள் பயனுள்ளதாக இருக்கும். எடுத்துக்காட்டாக, ஒரு முக்கிய த்ரெட் பல தொழிலாளி த்ரெட்கள் தங்கள் ஆரம்ப அமைப்பை முடித்துவிட்டதாக சமிக்ஞை செய்யும் வரை காத்திருக்கலாம், அதற்குப் பிறகு அது பணிகளை அனுப்பத் தொடங்கும்.
குறியீடு எடுத்துக்காட்டு 4: எளிய சமிக்ஞைக்காக threading.Event ஐப் பயன்படுத்தும் தயாரிப்பாளர்-நுகர்வோர் காட்சி
import threading
import time
import random
def producer(event, data_container):
for i in range(5):
item = f"Data-Item-{i}"
time.sleep(random.uniform(0.5, 1.5)) # Simulate work
data_container.append(item)
print(f"Producer: Produced {item}. Signaling consumer.")
event.set() # Signal that data is available
time.sleep(0.1) # Give consumer a chance to pick it up
event.clear() # Clear the flag for the next item, if applicable
def consumer(event, data_container):
for i in range(5):
print(f"Consumer: Waiting for data...")
event.wait() # Wait until the event is set
# At this point, event is set, data is ready
if data_container:
item = data_container.pop(0)
print(f"Consumer: Consumed {item}.")
else:
print("Consumer: Event was set but no data found. Possible race?")
# For simplicity, we assume producer clears the event after a short delay
if __name__ == "__main__":
data = [] # Shared data container (a list, not inherently thread-safe without locks)
data_ready_event = threading.Event()
producer_thread = threading.Thread(target=producer, args=(data_ready_event, data))
consumer_thread = threading.Thread(target=consumer, args=(data_ready_event, data))
producer_thread.start()
consumer_thread.start()
producer_thread.join()
consumer_thread.join()
print("Producer and Consumer finished.")
இந்த எளிமைப்படுத்தப்பட்ட எடுத்துக்காட்டில், producer தரவை உருவாக்கி, பின்னர் consumer க்கு சமிக்ஞை செய்ய event.set() ஐ அழைக்கிறது. consumer event.wait() ஐ அழைக்கிறது, இது event.set() அழைக்கப்படும் வரை தடுக்கிறது. நுகர்ந்த பிறகு, தயாரிப்பாளர் கொடியை மீட்டமைக்க event.clear() ஐ அழைக்கிறார். இது நிகழ்வு பயன்பாட்டை நிரூபித்தாலும், வலுவான தயாரிப்பாளர்-நுகர்வோர் பேட்டர்ன்களுக்கு, குறிப்பாக பகிரப்பட்ட தரவுக் கட்டமைப்புகளுடன், queue தொகுதி (பின்னர் விவாதிக்கப்படும்) பெரும்பாலும் மிகவும் வலுவான மற்றும் இயல்பாகவே த்ரெட்-பாதுகாப்பான தீர்வை வழங்குகிறது. இந்த எடுத்துக்காட்டு முதன்மையாக சமிக்ஞையைக் காட்டுகிறது, அவசியமாக முழுமையாக த்ரெட்-பாதுகாப்பான தரவுக் கையாளுதலைத் தானாகவே அல்ல.
நிபந்தனைகள் (Conditions)
ஒரு Condition ஆப்ஜெக்ட் என்பது மிகவும் மேம்பட்ட ஒத்திசைவு பிரிமிட்டிவ் ஆகும், இது ஒரு த்ரெட் ஒரு குறிப்பிட்ட நிபந்தனை பூர்த்தி செய்யப்படும் வரை காத்திருக்க வேண்டியிருக்கும் போது அடிக்கடி பயன்படுத்தப்படுகிறது, மேலும் அந்த நிபந்தனை உண்மையாக இருக்கும்போது மற்றொரு த்ரெட் அதற்கு அறிவிக்கிறது. இது ஒரு Lock இன் செயல்பாட்டை மற்ற த்ரெட்களுக்கு காத்திருக்கும் அல்லது அறிவிக்கும் திறனுடன் இணைக்கிறது. ஒரு Condition ஆப்ஜெக்ட் எப்போதும் ஒரு லாக்குடன் தொடர்புடையது. wait(), notify(), அல்லது notify_all() ஐ அழைப்பதற்கு முன்பு இந்த லாக் பெறப்பட வேண்டும்.
சிக்கலான தயாரிப்பாளர்-நுகர்வோர் மாதிரிகள், வள மேலாண்மை அல்லது பகிரப்பட்ட தரவின் நிலையின் அடிப்படையில் த்ரெட்கள் தொடர்பு கொள்ள வேண்டிய எந்தவொரு காட்சிக்கும் நிபந்தனைகள் சக்திவாய்ந்தவை. ஒரு எளிய கொடியான Event ஐப் போலல்லாமல், Condition மிகவும் நுணுக்கமான சமிக்ஞை மற்றும் காத்திருப்பை அனுமதிக்கிறது, பகிரப்பட்ட தரவின் நிலையிலிருந்து பெறப்பட்ட குறிப்பிட்ட, சிக்கலான தர்க்கரீதியான நிபந்தனைகளில் த்ரெட்கள் காத்திருக்க உதவுகிறது.
குறியீடு எடுத்துக்காட்டு 5: அதிநவீன ஒத்திசைவுக்காக threading.Condition ஐப் பயன்படுத்தும் தயாரிப்பாளர்-நுகர்வோர்
import threading
import time
import random
# A list protected by a lock within the condition
shared_data = []
condition = threading.Condition() # Condition object with an implicit Lock
class Producer(threading.Thread):
def run(self):
for i in range(5):
item = f"Product-{i}"
time.sleep(random.uniform(0.5, 1.5))
with condition: # Acquire the lock associated with the condition
shared_data.append(item)
print(f"Producer: Produced {item}. Signaled consumers.")
condition.notify_all() # Notify all waiting consumers
# In this specific simple case, notify_all is used, but notify()
# could also be used if only one consumer is expected to pick up.
class Consumer(threading.Thread):
def run(self):
for i in range(5):
with condition: # Acquire the lock
while not shared_data: # Wait until data is available
print(f"Consumer: No data, waiting...")
condition.wait() # Release lock and wait for notification
item = shared_data.pop(0)
print(f"Consumer: Consumed {item}.")
if __name__ == "__main__":
producer_thread = Producer()
consumer_thread1 = Consumer()
consumer_thread2 = Consumer() # Multiple consumers
producer_thread.start()
consumer_thread1.start()
consumer_thread2.start()
producer_thread.join()
consumer_thread1.join()
consumer_thread2.join()
print("All producer and consumer threads finished.")
இந்த எடுத்துக்காட்டில், condition shared_data ஐப் பாதுகாக்கிறது. Producer ஒரு பொருளைச் சேர்த்து, பின்னர் காத்திருக்கும் எந்த Consumer த்ரெட்களையும் எழுப்ப condition.notify_all() ஐ அழைக்கிறது. ஒவ்வொரு Consumer உம் நிபந்தனையின் லாக்கைப் பெறுகிறது, பின்னர் while not shared_data: சுழற்சியில் நுழைகிறது, தரவு இன்னும் கிடைக்கவில்லை என்றால் condition.wait() ஐ அழைக்கிறது. condition.wait() அணுக்கருவியாக லாக்கை வெளியிட்டு, மற்றொரு த்ரெட்டால் notify() அல்லது notify_all() அழைக்கப்படும் வரை தடுக்கிறது. எழுப்பப்பட்டதும், wait() திரும்புவதற்கு முன்பு லாக்கை மீண்டும் பெறுகிறது. இது பகிரப்பட்ட தரவு பாதுகாப்பாக அணுகப்பட்டு மாற்றப்படுவதையும், நுகர்வோர் தரவு உண்மையாகக் கிடைக்கும்போது மட்டுமே செயலாக்குவதையும் உறுதி செய்கிறது. இந்த பேட்டர்ன் அதிநவீன வேலை வரிசைகள் மற்றும் ஒத்திசைக்கப்பட்ட வள மேலாளர்களை உருவாக்குவதற்கு அடிப்படையானது.
த்ரெட்-பாதுகாப்பான தரவுக் கட்டமைப்புகளைச் செயல்படுத்துதல்
பைத்தானின் ஒத்திசைவு பிரிமிட்டிவ்கள் கட்டுமானத் தொகுதிகளை வழங்கும் அதே வேளையில், உண்மையிலேயே வலுவான கன்கரன்ட் பயன்பாடுகளுக்கு பெரும்பாலும் பொதுவான தரவுக் கட்டமைப்புகளின் த்ரெட்-பாதுகாப்பான பதிப்புகள் தேவைப்படுகின்றன. உங்கள் பயன்பாட்டுக் குறியீடு முழுவதும் Lock பெறுதல்/வெளியிடுதல் அழைப்புகளை சிதறடிப்பதற்குப் பதிலாக, ஒத்திசைவு தர்க்கத்தை தரவுக் கட்டமைப்பிற்குள்ளேயே இணைப்பது பொதுவாக சிறந்த நடைமுறையாகும். இந்த அணுகுமுறை மட்டுப்படுத்தலை ஊக்குவிக்கிறது, தவறவிட்ட லாக்குகளின் சாத்தியக்கூறுகளைக் குறைக்கிறது, மேலும் உங்கள் குறியீட்டைப் பற்றி பகுத்தறியவும் பராமரிக்கவும் எளிதாக்குகிறது, குறிப்பாக சிக்கலான, உலகளவில் விநியோகிக்கப்பட்ட அமைப்புகளில்.
த்ரெட்-பாதுகாப்பான பட்டியல்கள் மற்றும் அகராதிகள்
பைத்தானின் உள்ளமைக்கப்பட்ட list மற்றும் dict வகைகள் கன்கரன்ட் மாற்றங்களுக்கு இயல்பாகவே த்ரெட்-பாதுகாப்பானவை அல்ல. append() அல்லது get() போன்ற செயல்பாடுகள் GIL காரணமாக அணுக்கருவியாகத் தோன்றினாலும், ஒருங்கிணைந்த செயல்பாடுகள் (எ.கா., உறுப்பு உள்ளதா எனச் சரிபார்த்து, இல்லையென்றால் சேர்க்கவும்) அவ்வாறு இல்லை. அவற்றை த்ரெட்-பாதுகாப்பானதாக மாற்ற, நீங்கள் அனைத்து அணுகல் மற்றும் மாற்றியமைத்தல் முறைகளையும் ஒரு லாக்குடன் பாதுகாக்க வேண்டும்.
குறியீடு எடுத்துக்காட்டு 6: ஒரு எளிய ThreadSafeList வகுப்பு
import threading
class ThreadSafeList:
def __init__(self):
self._list = []
self._lock = threading.Lock()
def append(self, item):
with self._lock:
self._list.append(item)
def pop(self):
with self._lock:
if not self._list:
raise IndexError("pop from empty list")
return self._list.pop()
def __getitem__(self, index):
with self._lock:
return self._list[index]
def __setitem__(self, index, value):
with self._lock:
self._list[index] = value
def __len__(self):
with self._lock:
return len(self._list)
def __contains__(self, item):
with self._lock:
return item in self._list
def __str__(self):
with self._lock:
return str(self._list)
# You would need to add similar methods for insert, remove, extend, etc.
if __name__ == "__main__":
ts_list = ThreadSafeList()
def list_worker(list_obj, items_to_add):
for item in items_to_add:
list_obj.append(item)
print(f"Thread {threading.current_thread().name} added {len(items_to_add)} items.")
thread1_items = ["A", "B", "C"]
thread2_items = ["X", "Y", "Z"]
t1 = threading.Thread(target=list_worker, args=(ts_list, thread1_items), name="Thread-1")
t2 = threading.Thread(target=list_worker, args=(ts_list, thread2_items), name="Thread-2")
t1.start()
t2.start()
t1.join()
t2.join()
print(f"Final ThreadSafeList: {ts_list}")
print(f"Final length: {len(ts_list)}")
# The order of items might vary, but all items will be present, and length will be correct.
assert len(ts_list) == len(thread1_items) + len(thread2_items)
இந்த ThreadSafeList ஒரு நிலையான பைதான் பட்டியலைச் சுற்றி ஒரு threading.Lock ஐப் பயன்படுத்தி அனைத்து மாற்றங்களும் அணுகல்களும் அணுக்கருவியாக இருப்பதை உறுதி செய்கிறது. self._list ஐப் படிக்கும் அல்லது எழுதும் எந்த முறையும் முதலில் லாக்கைப் பெறுகிறது. இந்த பேட்டர்னை ThreadSafeDict அல்லது பிற தனிப்பயன் தரவுக் கட்டமைப்புகளுக்கு நீட்டிக்கலாம். திறமையானதாக இருந்தாலும், இந்த அணுகுமுறை நிலையான லாக் போட்டி காரணமாக செயல்திறன் மேல்நிலையை அறிமுகப்படுத்தலாம், குறிப்பாக செயல்பாடுகள் அடிக்கடி மற்றும் குறுகிய காலம் நீடித்தால்.
திறமையான க்யூக்களுக்கு collections.deque ஐப் பயன்படுத்துதல்
collections.deque (இரட்டை முனைக் க்யூ) என்பது ஒரு உயர் செயல்திறன் கொண்ட பட்டியல் போன்ற கொள்கலன் ஆகும், இது இரு முனைகளிலிருந்தும் வேகமான சேர்ப்புகளையும் நீக்கங்களையும் அனுமதிக்கிறது. இந்த செயல்பாடுகளுக்கு அதன் O(1) நேர சிக்கலான தன்மை காரணமாக இது ஒரு க்யூவின் அடிப்படை தரவுக் கட்டமைப்பாக ஒரு சிறந்த தேர்வாகும், இது ஒரு நிலையான list ஐ விட க்யூ போன்ற பயன்பாட்டிற்கு மிகவும் திறமையானதாக ஆக்குகிறது, குறிப்பாக க்யூ பெரியதாக வளரும்போது.
இருப்பினும், collections.deque என்பதே கன்கரன்ட் மாற்றங்களுக்கு த்ரெட்-பாதுகாப்பானது அல்ல. பல த்ரெட்கள் ஒரே deque நிகழ்வில் ஒரே நேரத்தில் append() அல்லது popleft() ஐ வெளிப்புற ஒத்திசைவு இல்லாமல் அழைத்தால், ரேஸ் கண்டிஷன்கள் ஏற்படலாம். எனவே, ஒரு மல்டித்ரெடிங் சூழலில் deque ஐப் பயன்படுத்தும்போது, ThreadSafeList எடுத்துக்காட்டைப் போலவே, அதன் முறைகளை ஒரு threading.Lock அல்லது threading.Condition உடன் பாதுகாக்க வேண்டும். இது இருந்தபோதிலும், க்யூ செயல்பாடுகளுக்கான அதன் செயல்திறன் பண்புகள், நிலையான queue தொகுதியின் சலுகைகள் போதுமானதாக இல்லாதபோது தனிப்பயன் த்ரெட்-பாதுகாப்பான க்யூக்களுக்கான உள் செயலாக்கமாக அதை ஒரு சிறந்த தேர்வாக ஆக்குகின்றன.
தயாரிப்புக்குத் தயாரான கட்டமைப்புகளுக்கு queue தொகுதியின் சக்தி
பெரும்பாலான பொதுவான தயாரிப்பாளர்-நுகர்வோர் பேட்டர்ன்களுக்கு, பைத்தானின் ஸ்டாண்டர்டு லைப்ரரி queue தொகுதியை வழங்குகிறது, இது பல இயல்பாகவே த்ரெட்-பாதுகாப்பான க்யூ செயலாக்கங்களை வழங்குகிறது. இந்த வகுப்புகள் தேவையான அனைத்து லாக்கிங் மற்றும் சமிக்ஞைகளை உள்நாட்டில் கையாளுகின்றன, இது டெவலப்பரை குறைந்த-நிலை ஒத்திசைவு பிரிமிட்டிவ்களை நிர்வகிப்பதில் இருந்து விடுவிக்கிறது. இது கன்கரன்ட் குறியீட்டை கணிசமாக எளிதாக்குகிறது மற்றும் ஒத்திசைவு பிழைகளின் அபாயத்தைக் குறைக்கிறது.
queue தொகுதி உள்ளடக்கியது:
queue.Queue: முதலில் வருவது, முதலில் செல்வது (FIFO) க்யூ. பொருட்கள் சேர்க்கப்பட்ட வரிசையில் மீட்டெடுக்கப்படுகின்றன.queue.LifoQueue: கடைசியில் வருவது, முதலில் செல்வது (LIFO) க்யூ, ஒரு ஸ்டேக் போல செயல்படுகிறது.queue.PriorityQueue: அவற்றின் முன்னுரிமையின் அடிப்படையில் பொருட்களை மீட்டெடுக்கும் ஒரு க்யூ (குறைந்த முன்னுரிமை மதிப்பு முதலில்). பொருட்கள் பொதுவாக டியூபிள்களாக இருக்கும்(முன்னுரிமை, தரவு).
இந்த க்யூ வகைகள் வலுவான மற்றும் அளவிடக்கூடிய கன்கரன்ட் அமைப்புகளை உருவாக்குவதற்கு இன்றியமையாதவை. அவை தொழிலாளி த்ரெட்களின் ஒரு குளத்திற்கு பணிகளை விநியோகிப்பதற்கும், சேவைகளுக்கு இடையில் செய்தி அனுப்புதலை நிர்வகிப்பதற்கும், அல்லது ஒரு உலகளாவிய பயன்பாட்டில் ஒத்திசைவற்ற செயல்பாடுகளைக் கையாளுவதற்கும் குறிப்பாக மதிப்புமிக்கவை, அங்கு பணிகள் பல்வேறு மூலங்களிலிருந்து வரலாம் மற்றும் நம்பகத்தன்மையுடன் செயலாக்கப்பட வேண்டும்.
குறியீடு எடுத்துக்காட்டு 7: queue.Queue ஐப் பயன்படுத்தும் தயாரிப்பாளர்-நுகர்வோர்
import threading
import queue
import time
import random
def producer_queue(q, num_items):
for i in range(num_items):
item = f"Order-{i:03d}"
time.sleep(random.uniform(0.1, 0.5)) # Simulate generating an order
q.put(item) # Put item into the queue (blocks if queue is full)
print(f"Producer: Placed {item} in queue.")
def consumer_queue(q, thread_id):
while True:
try:
item = q.get(timeout=1) # Get item from queue (blocks if queue is empty)
print(f"Consumer {thread_id}: Processing {item}...")
time.sleep(random.uniform(0.5, 1.5)) # Simulate processing the order
q.task_done() # Signal that the task for this item is complete
except queue.Empty:
print(f"Consumer {thread_id}: Queue empty, exiting.")
break
if __name__ == "__main__":
q = queue.Queue(maxsize=10) # A queue with a maximum size
num_producers = 2
num_consumers = 3
items_per_producer = 5
producer_threads = []
for i in range(num_producers):
t = threading.Thread(target=producer_queue, args=(q, items_per_producer), name=f"Producer-{i+1}")
producer_threads.append(t)
t.start()
consumer_threads = []
for i in range(num_consumers):
t = threading.Thread(target=consumer_queue, args=(q, i+1), name=f"Consumer-{i+1}")
consumer_threads.append(t)
t.start()
# Wait for producers to finish
for t in producer_threads:
t.join()
# Wait for all items in the queue to be processed
q.join() # Blocks until all items in the queue have been gotten and task_done() has been called for them
# Signal consumers to exit by using the timeout on get()
# Or, a more robust way would be to put a "sentinel" object (e.g., None) into the queue
# for each consumer and have consumers exit when they see it.
# For this example, the timeout is used, but sentinel is generally safer for indefinite consumers.
for t in consumer_threads:
t.join() # Wait for consumers to finish their timeout and exit
print("All production and consumption complete.")
இந்த எடுத்துக்காட்டு queue.Queue இன் நேர்த்தியையும் பாதுகாப்பையும் தெளிவாக நிரூபிக்கிறது. தயாரிப்பாளர்கள் Order-XXX பொருட்களை க்யூவில் வைக்கிறார்கள், மற்றும் நுகர்வோர் அவற்றை ஒரே நேரத்தில் மீட்டெடுத்து செயலாக்குகிறார்கள். q.put() மற்றும் q.get() முறைகள் இயல்பாகவே தடுப்பவை, தயாரிப்பாளர்கள் ஒரு முழு க்யூவில் சேர்க்காமல் இருப்பதையும், நுகர்வோர் ஒரு காலி க்யூவிலிருந்து மீட்டெடுக்க முயற்சிக்காமல் இருப்பதையும் உறுதி செய்கின்றன, இதனால் ரேஸ் கண்டிஷன்களைத் தடுத்து சரியான ஓட்டக் கட்டுப்பாட்டை உறுதி செய்கின்றன. q.task_done() மற்றும் q.join() முறைகள் சமர்ப்பிக்கப்பட்ட அனைத்து பணிகளும் செயலாக்கப்படும் வரை காத்திருக்க ஒரு வலுவான வழிமுறையை வழங்குகின்றன, இது கன்கரன்ட் பணிப்பாய்வுகளின் வாழ்க்கைச் சுழற்சியை ஒரு கணிக்கக்கூடிய முறையில் நிர்வகிப்பதற்கு முக்கியமானது.
collections.Counter மற்றும் த்ரெட் பாதுகாப்பு
collections.Counter என்பது ஹாஷ் செய்யக்கூடிய ஆப்ஜெக்ட்களை எண்ணுவதற்கு ஒரு வசதியான அகராதி துணைப்பிரிவு ஆகும். அதன் தனிப்பட்ட செயல்பாடுகளான update() அல்லது __getitem__ பொதுவாக திறமையானதாக வடிவமைக்கப்பட்டிருந்தாலும், பல த்ரெட்கள் ஒரே கவுண்டர் நிகழ்வை ஒரே நேரத்தில் மாற்றினால் Counter என்பதே இயல்பாகவே த்ரெட்-பாதுகாப்பானது அல்ல. எடுத்துக்காட்டாக, இரண்டு த்ரெட்கள் ஒரே பொருளின் எண்ணிக்கையை அதிகரிக்க முயற்சித்தால் (counter['item'] += 1), ஒரு ரேஸ் கண்டிஷன் ஏற்படலாம், அங்கு ஒரு அதிகரிப்பு இழக்கப்படலாம்.
மாற்றங்கள் நடக்கும் ஒரு மல்டித்ரெடிங் சூழலில் collections.Counter ஐ த்ரெட்-பாதுகாப்பானதாக மாற்ற, நீங்கள் அதன் மாற்றியமைத்தல் முறைகளை (அல்லது அதை மாற்றும் எந்த குறியீட்டுத் தொகுதியையும்) ஒரு threading.Lock உடன் மூட வேண்டும், நாங்கள் ThreadSafeList உடன் செய்தது போலவே.
த்ரெட்-பாதுகாப்பான கவுண்டருக்கான குறியீடு எடுத்துக்காட்டு (கருத்து, அகராதி செயல்பாடுகளுடன் SafeCounter ஐப் போன்றது)
import threading
from collections import Counter
import time
class ThreadSafeCounterCollection:
def __init__(self):
self._counter = Counter()
self._lock = threading.Lock()
def increment(self, item, amount=1):
with self._lock:
self._counter[item] += amount
def get_count(self, item):
with self._lock:
return self._counter[item]
def total_count(self):
with self._lock:
return sum(self._counter.values())
def __str__(self):
with self._lock:
return str(self._counter)
def counter_worker(ts_counter_collection, items, num_iterations):
for _ in range(num_iterations):
for item in items:
ts_counter_collection.increment(item)
time.sleep(0.00001) # Small delay to increase chance of interleaving
if __name__ == "__main__":
ts_coll = ThreadSafeCounterCollection()
products_for_thread1 = ["Laptop", "Monitor"]
products_for_thread2 = ["Keyboard", "Mouse", "Laptop"] # Overlap on 'Laptop'
num_threads = 5
iterations = 1000
threads = []
for i in range(num_threads):
# Alternate items to ensure contention
items_to_use = products_for_thread1 if i % 2 == 0 else products_for_thread2
t = threading.Thread(target=counter_worker, args=(ts_coll, items_to_use, iterations), name=f"Worker-{i}")
threads.append(t)
t.start()
for t in threads:
t.join()
print(f"Final counts: {ts_coll}")
# Calculate expected for Laptop: 3 threads processed Laptop from products_for_thread2, 2 from products_for_thread1
# Expected Laptop = (3 * iterations) + (2 * iterations) = 5 * iterations
# If the logic for items_to_use is:
# 0 -> ["Laptop", "Monitor"]
# 1 -> ["Keyboard", "Mouse", "Laptop"]
# 2 -> ["Laptop", "Monitor"]
# 3 -> ["Keyboard", "Mouse", "Laptop"]
# 4 -> ["Laptop", "Monitor"]
# Laptop: 3 threads from products_for_thread1, 2 from products_for_thread2 = 5 * iterations
# Monitor: 3 * iterations
# Keyboard: 2 * iterations
# Mouse: 2 * iterations
expected_laptop = 5 * iterations
expected_monitor = 3 * iterations
expected_keyboard = 2 * iterations
expected_mouse = 2 * iterations
print(f"Expected Laptop count: {expected_laptop}")
print(f"Actual Laptop count: {ts_coll.get_count('Laptop')}")
assert ts_coll.get_count('Laptop') == expected_laptop, "Laptop count mismatch!"
assert ts_coll.get_count('Monitor') == expected_monitor, "Monitor count mismatch!"
assert ts_coll.get_count('Keyboard') == expected_keyboard, "Keyboard count mismatch!"
assert ts_coll.get_count('Mouse') == expected_mouse, "Mouse count mismatch!"
print("Thread-safe CounterCollection validated.")
இந்த ThreadSafeCounterCollection collections.Counter ஐ ஒரு threading.Lock உடன் மூடுவது எப்படி என்பதைக் காட்டுகிறது, அனைத்து மாற்றங்களும் அணுக்கருவியாக இருப்பதை உறுதி செய்கிறது. ஒவ்வொரு increment செயல்பாடும் லாக்கைப் பெறுகிறது, Counter புதுப்பிப்பைச் செய்கிறது, பின்னர் லாக்கை வெளியிடுகிறது. இந்த பேட்டர்ன் பல த்ரெட்கள் ஒரே நேரத்தில் ஒரே பொருட்களைப் புதுப்பிக்க முயற்சிக்கும்போதும், இறுதி எண்ணிக்கைகள் துல்லியமாக இருப்பதை உறுதி செய்கிறது. இது நிகழ்நேர பகுப்பாய்வு, பதிவு செய்தல் அல்லது உலகளாவிய பயனர் தளத்திலிருந்து பயனர் தொடர்புகளைக் கண்காணிப்பது போன்ற காட்சிகளில் குறிப்பாக பொருத்தமானது, அங்கு ஒட்டுமொத்த புள்ளிவிவரங்கள் துல்லியமாக இருக்க வேண்டும்.
ஒரு த்ரெட்-பாதுகாப்பான கேஷை செயல்படுத்துதல்
கேஷிங் என்பது பயன்பாடுகளின் செயல்திறனையும் பதிலளிக்கக்கூடிய தன்மையையும் மேம்படுத்துவதற்கான ஒரு முக்கியமான தேர்வுமுறை நுட்பமாகும், குறிப்பாக உலகளாவிய பார்வையாளர்களுக்கு சேவை செய்யும் பயன்பாடுகளுக்கு, அங்கு தாமதத்தைக் குறைப்பது மிக முக்கியமானது. ஒரு கேஷ் அடிக்கடி அணுகப்படும் தரவைச் சேமிக்கிறது, இது விலை உயர்ந்த மறு கணக்கீடு அல்லது தரவுத்தளங்கள் அல்லது வெளிப்புற API கள் போன்ற மெதுவான மூலங்களிலிருந்து மீண்டும் மீண்டும் தரவைப் பெறுவதைத் தவிர்க்கிறது. ஒரு கன்கரன்ட் சூழலில், படித்தல், எழுதுதல் மற்றும் வெளியேற்றும் செயல்பாடுகளின் போது ரேஸ் கண்டிஷன்களைத் தடுக்க ஒரு கேஷ் த்ரெட்-பாதுகாப்பானதாக இருக்க வேண்டும். ஒரு பொதுவான கேஷ் பேட்டர்ன் LRU (குறைந்தபட்சம் சமீபத்தில் பயன்படுத்தப்பட்டது) ஆகும், அங்கு கேஷ் அதன் திறனை அடையும்போது மிகப் பழமையான அல்லது குறைந்தபட்சம் சமீபத்தில் அணுகப்பட்ட பொருட்கள் அகற்றப்படுகின்றன.
குறியீடு எடுத்துக்காட்டு 8: ஒரு அடிப்படை ThreadSafeLRUCache (எளிமைப்படுத்தப்பட்டது)
import threading
from collections import OrderedDict
import time
class ThreadSafeLRUCache:
def __init__(self, capacity):
self.capacity = capacity
self.cache = OrderedDict() # OrderedDict maintains insertion order (useful for LRU)
self.lock = threading.Lock()
def get(self, key):
with self.lock:
if key not in self.cache:
return None
value = self.cache.pop(key) # Remove and re-insert to mark as recently used
self.cache[key] = value
return value
def put(self, key, value):
with self.lock:
if key in self.cache:
self.cache.pop(key) # Remove old entry to update
elif len(self.cache) >= self.capacity:
self.cache.popitem(last=False) # Remove LRU item
self.cache[key] = value
def __len__(self):
with self.lock:
return len(self.cache)
def __str__(self):
with self.lock:
return str(self.cache)
def cache_worker(cache_obj, worker_id, keys_to_access):
for i, key in enumerate(keys_to_access):
# Simulate read/write operations
if i % 2 == 0: # Half reads
value = cache_obj.get(key)
print(f"Worker {worker_id}: Get '{key}' -> {value}")
else: # Half writes
cache_obj.put(key, f"Value-{worker_id}-{key}")
print(f"Worker {worker_id}: Put '{key}'")
time.sleep(0.01) # Simulate some work
if __name__ == "__main__":
lru_cache = ThreadSafeLRUCache(capacity=3)
keys_t1 = ["data_a", "data_b", "data_c", "data_a"] # Re-access data_a
keys_t2 = ["data_d", "data_e", "data_c", "data_b"] # Access new and existing
t1 = threading.Thread(target=cache_worker, args=(lru_cache, 1, keys_t1), name="Cache-Worker-1")
t2 = threading.Thread(target=cache_worker, args=(lru_cache, 2, keys_t2), name="Cache-Worker-2")
t1.start()
t2.start()
t1.join()
t2.join()
print(f"\nFinal Cache State: {lru_cache}")
print(f"Cache Size: {len(lru_cache)}")
# Verify state (example: 'data_c' and 'data_b' should be present, 'data_a' potentially evicted by 'data_d', 'data_e')
# The exact state can vary due to interleaving of put/get.
# The key is that operations happen without corruption.
# Let's assume after the example runs, "data_e", "data_c", "data_b" might be the last 3 accessed
# Or "data_d", "data_e", "data_c" if t2's puts come later.
# "data_a" will likely be evicted if no other puts happen after its last get by t1.
print(f"Is 'data_e' in cache? {lru_cache.get('data_e') is not None}")
print(f"Is 'data_a' in cache? {lru_cache.get('data_a') is not None}")
இந்த ThreadSafeLRUCache வகுப்பு பொருள் வரிசையை நிர்வகிக்க (LRU வெளியேற்றத்திற்கு) collections.OrderedDict ஐப் பயன்படுத்துகிறது மற்றும் அனைத்து get, put, மற்றும் __len__ செயல்பாடுகளையும் ஒரு threading.Lock உடன் பாதுகாக்கிறது. ஒரு பொருள் get வழியாக அணுகப்படும்போது, அது அகற்றப்பட்டு மீண்டும் செருகப்படுகிறது, அதை "மிகவும் சமீபத்தில் பயன்படுத்தப்பட்ட" முனைக்கு நகர்த்துகிறது. put அழைக்கப்பட்டு கேஷ் நிரம்பியிருக்கும்போது, popitem(last=False) "குறைந்தபட்சம் சமீபத்தில் பயன்படுத்தப்பட்ட" பொருளை மறுமுனையிலிருந்து நீக்குகிறது. இது கேஷின் நேர்மை மற்றும் LRU தர்க்கம் அதிக கன்கரன்ட் சுமையின் கீழும் பாதுகாக்கப்படுவதை உறுதி செய்கிறது, இது உலகளவில் விநியோகிக்கப்பட்ட சேவைகளுக்கு இன்றியமையாதது, அங்கு கேஷ் நிலைத்தன்மை செயல்திறன் மற்றும் துல்லியத்திற்கு மிக முக்கியமானது.
உலகளாவிய வரிசைப்படுத்தல்களுக்கான மேம்பட்ட பேட்டர்ன்ஸ் மற்றும் பரிசீலனைகள்
அடிப்படை பிரிமிட்டிவ்கள் மற்றும் அடிப்படை த்ரெட்-பாதுகாப்பான கட்டமைப்புகளுக்கு அப்பால், ஒரு உலகளாவிய பார்வையாளர்களுக்காக வலுவான கன்கரன்ட் பயன்பாடுகளை உருவாக்குவதற்கு மேலும் மேம்பட்ட கவலைகளுக்கு கவனம் தேவை. இவற்றில் பொதுவான கன்கரன்சி ஆபத்துக்களைத் தடுப்பது, செயல்திறன் பரிமாற்றங்களைப் புரிந்துகொள்வது மற்றும் மாற்று கன்கரன்சி மாதிரிகளை எப்போது பயன்படுத்துவது என்பதை அறிவது ஆகியவை அடங்கும்.
டெட்லாக்குகள் மற்றும் அவற்றை எப்படி தவிர்ப்பது
ஒரு டெட்லாக் என்பது இரண்டு அல்லது அதற்கு மேற்பட்ட த்ரெட்கள் காலவரையின்றி தடுக்கப்பட்ட ஒரு நிலையாகும், ஒவ்வொன்றும் தங்களுக்குத் தேவையான வளங்களை மற்றொன்று வெளியிடுவதற்காகக் காத்திருக்கின்றன. பல த்ரெட்கள் பல லாக்குகளைப் பெற வேண்டியிருக்கும் போது இது பொதுவாக நிகழ்கிறது, மேலும் அவை வெவ்வேறு வரிசைகளில் அவ்வாறு செய்கின்றன. டெட்லாக்குகள் முழு பயன்பாடுகளையும் நிறுத்திவிடலாம், இது பதிலளிக்காத தன்மை மற்றும் சேவை செயலிழப்புகளுக்கு வழிவகுக்கும், இது குறிப்பிடத்தக்க உலகளாவிய தாக்கத்தை ஏற்படுத்தும்.
ஒரு டெட்லாக்கிற்கான உன்னதமான காட்சி இரண்டு த்ரெட்கள் மற்றும் இரண்டு லாக்குகளை உள்ளடக்கியது:
- த்ரெட் A லாக் 1 ஐப் பெறுகிறது.
- த்ரெட் B லாக் 2 ஐப் பெறுகிறது.
- த்ரெட் A லாக் 2 ஐப் பெற முயற்சிக்கிறது (மற்றும் B க்காகக் காத்திருந்து தடுக்கப்படுகிறது).
- த்ரெட் B லாக் 1 ஐப் பெற முயற்சிக்கிறது (மற்றும் A க்காகக் காத்திருந்து தடுக்கப்படுகிறது). இரண்டு த்ரெட்களும் இப்போது சிக்கிக்கொண்டன, மற்றொன்றால் பிடிக்கப்பட்ட ஒரு வளத்திற்காகக் காத்திருக்கின்றன.
டெட்லாக்குகளைத் தவிர்ப்பதற்கான உத்திகள்:
- நிலையான லாக் வரிசைமுறை: லாக்குகளைப் பெறுவதற்கு ஒரு கடுமையான, உலகளாவிய வரிசையை நிறுவி, அனைத்து த்ரெட்களும் அதே வரிசையில் அவற்றைப் பெறுவதை உறுதி செய்வதே மிகவும் பயனுள்ள வழியாகும். த்ரெட் A எப்போதும் லாக் 1 ஐப் பெற்று பின்னர் லாக் 2 ஐப் பெற்றால், த்ரெட் B யும் லாக் 1 ஐப் பெற்று பின்னர் லாக் 2 ஐப் பெற வேண்டும், ஒருபோதும் லாக் 2 ஐப் பெற்று பின்னர் லாக் 1 ஐப் பெறக்கூடாது.
- நெஸ்டட் லாக்குகளைத் தவிர்க்கவும்: முடிந்தவரை, ஒரு த்ரெட் ஒரே நேரத்தில் பல லாக்குகளை வைத்திருக்க வேண்டிய காட்சிகளைக் குறைக்க அல்லது தவிர்க்க உங்கள் பயன்பாட்டை வடிவமைக்கவும்.
- மீண்டும் நுழையும் தன்மை தேவைப்படும்போது
RLockஐப் பயன்படுத்தவும்: முன்னர் குறிப்பிட்டபடி,RLockஒரு த்ரெட் ஒரே லாக்கை பல முறை பெற முயற்சித்தால் தன்னைத்தானே டெட்லாக் செய்வதைத் தடுக்கிறது. இருப்பினும்,RLockவெவ்வேறு த்ரெட்களுக்கு இடையில் டெட்லாக்குகளைத் தடுக்காது. - காலக்கெடு வாதங்கள்: பல ஒத்திசைவு பிரிமிட்டிவ்கள் (
Lock.acquire(),Queue.get(),Queue.put()) ஒருtimeoutவாதத்தை ஏற்றுக்கொள்கின்றன. ஒரு லாக் அல்லது வளம் குறிப்பிட்ட காலக்கெடுவுக்குள் பெறப்படாவிட்டால், அழைப்புFalseஐத் தரும் அல்லது ஒரு விதிவிலக்கை எழுப்பும் (queue.Empty,queue.Full). இது த்ரெட்டை மீட்கவும், சிக்கலைப் பதிவு செய்யவும் அல்லது மீண்டும் முயற்சிக்கவும் அனுமதிக்கிறது, காலவரையின்றி தடுப்பதற்குப் பதிலாக. இது ஒரு தடுப்பு அல்ல என்றாலும், இது டெட்லாக்குகளை மீட்கக்கூடியதாக மாற்றும். - அணுக்கருவித்தன்மைக்கு வடிவமைப்பு: முடிந்தவரை, செயல்பாடுகளை அணுக்கருவியாக வடிவமைக்கவும் அல்லது
queueதொகுதி போன்ற உயர்-நிலை, இயல்பாகவே த்ரெட்-பாதுகாப்பான சுருக்கங்களைப் பயன்படுத்தவும், அவை அவற்றின் உள் வழிமுறைகளில் டெட்லாக்குகளைத் தவிர்க்க வடிவமைக்கப்பட்டுள்ளன.
கன்கரன்ட் செயல்பாடுகளில் ஐடம்பொட்டன்சி (Idempotency)
ஐடம்பொட்டன்சி என்பது ஒரு செயல்பாட்டின் பண்பு ஆகும், அங்கு அதை பல முறை பயன்படுத்துவது அதை ஒரு முறை பயன்படுத்துவதைப் போன்ற அதே முடிவைத் தருகிறது. கன்கரன்ட் மற்றும் விநியோகிக்கப்பட்ட அமைப்புகளில், தற்காலிக நெட்வொர்க் சிக்கல்கள், காலக்கெடு அல்லது அமைப்பு தோல்விகள் காரணமாக செயல்பாடுகள் மீண்டும் முயற்சிக்கப்படலாம். இந்த செயல்பாடுகள் ஐடம்பொட்டன்ட் இல்லையென்றால், மீண்டும் மீண்டும் செயல்படுத்துவது தவறான நிலைகள், நகல் தரவு அல்லது எதிர்பாராத பக்க விளைவுகளுக்கு வழிவகுக்கும்.
எடுத்துக்காட்டாக, ஒரு "இருப்பை அதிகரி" செயல்பாடு ஐடம்பொட்டன்ட் இல்லையென்றால், மற்றும் ஒரு நெட்வொர்க் பிழை ஒரு மறுமுயற்சிக்கு காரணமாக இருந்தால், ஒரு பயனரின் இருப்பு இரண்டு முறை பற்று வைக்கப்படலாம். ஒரு ஐடம்பொட்டன்ட் பதிப்பு, பற்று வைப்பதற்கு முன்பு குறிப்பிட்ட பரிவர்த்தனை ஏற்கனவே செயலாக்கப்பட்டுள்ளதா என்பதைச் சரிபார்க்கலாம். இது கண்டிப்பாக ஒரு கன்கரன்சி பேட்டர்ன் அல்ல என்றாலும், கன்கரன்ட் கூறுகளை ஒருங்கிணைக்கும்போது, குறிப்பாக உலகளாவிய கட்டமைப்புகளில் செய்தி அனுப்புதல் மற்றும் விநியோகிக்கப்பட்ட பரிவர்த்தனைகள் பொதுவானதாகவும், நெட்வொர்க் நம்பகத்தன்மை ஒரு கொடுக்கப்பட்டதாகவும் இருக்கும்போது ஐடம்பொட்டன்சிக்காக வடிவமைப்பது முக்கியமானது. இது தற்செயலான அல்லது வேண்டுமென்றே செய்யப்பட்ட, ஏற்கனவே பகுதியளவு அல்லது முழுமையாக முடிக்கப்பட்டிருக்கக்கூடிய செயல்பாடுகளின் மறுமுயற்சிகளின் விளைவுகளுக்கு எதிராகப் பாதுகாப்பதன் மூலம் த்ரெட் பாதுகாப்பை நிறைவு செய்கிறது.
லாக்கிங்கின் செயல்திறன் தாக்கங்கள்
த்ரெட் பாதுகாப்பிற்கு லாக்குகள் அவசியமானவை என்றாலும், அவை ஒரு செயல்திறன் செலவுடன் வருகின்றன.
- மேல்நிலை: லாக்குகளைப் பெறுவதும் வெளியிடுவதும் CPU சுழற்சிகளை உள்ளடக்கியது. அதிக போட்டி நிலவும் காட்சிகளில் (பல த்ரெட்கள் அடிக்கடி ஒரே லாக்கிற்காகப் போட்டியிடுகின்றன), இந்த மேல்நிலை குறிப்பிடத்தக்கதாக மாறும்.
- போட்டி: ஒரு த்ரெட் ஏற்கனவே பிடிக்கப்பட்ட ஒரு லாக்கைப் பெற முயற்சிக்கும்போது, அது தடுக்கப்படுகிறது, இது சூழல் மாற்றம் மற்றும் வீணான CPU நேரத்திற்கு வழிவகுக்கிறது. அதிக போட்டி ஒரு கன்கரன்ட் பயன்பாட்டை வரிசைப்படுத்தலாம், மல்டித்ரெடிங்கின் நன்மைகளை மறுக்கிறது.
- கிரானுலாரிட்டி:
- கரடுமுரடான லாக்கிங்: ஒரு பெரிய குறியீட்டுப் பகுதியை அல்லது ஒரு முழு தரவுக் கட்டமைப்பை ஒரு லாக்குடன் பாதுகாத்தல். செயல்படுத்த எளிதானது, ஆனால் அதிக போட்டிக்கு வழிவகுத்து கன்கரன்சியைக் குறைக்கலாம்.
- நுண்ணிய லாக்கிங்: குறியீட்டின் மிகச்சிறிய முக்கியமான பகுதிகளை அல்லது ஒரு தரவுக் கட்டமைப்பின் தனிப்பட்ட பகுதிகளை மட்டும் பாதுகாத்தல் (எ.கா., ஒரு இணைக்கப்பட்ட பட்டியலில் தனிப்பட்ட முனைகளைப் பூட்டுதல், அல்லது ஒரு அகராதியின் தனித்தனி பகுதிகள்). இது அதிக கன்கரன்சியை அனுமதிக்கிறது, ஆனால் கவனமாக நிர்வகிக்கப்படாவிட்டால் சிக்கலையும் டெட்லாக்குகளின் அபாயத்தையும் அதிகரிக்கிறது.
கரடுமுரடான மற்றும் நுண்ணிய லாக்கிங்கிற்கு இடையேயான தேர்வு எளிமைக்கும் செயல்திறனுக்கும் இடையேயான ஒரு பரிமாற்றமாகும். பெரும்பாலான பைதான் பயன்பாடுகளுக்கு, குறிப்பாக CPU வேலைக்காக GIL ஆல் கட்டுப்படுத்தப்பட்டவற்றுக்கு, queue தொகுதியின் த்ரெட்-பாதுகாப்பான கட்டமைப்புகளைப் பயன்படுத்துவது அல்லது I/O-பவுண்ட் பணிகளுக்கு கரடுமுரடான லாக்குகளைப் பயன்படுத்துவது பெரும்பாலும் சிறந்த சமநிலையை வழங்குகிறது. உங்கள் கன்கரன்ட் குறியீட்டை சுயவிவரப்படுத்துவது இடையூறுகளை அடையாளம் காணவும் லாக்கிங் உத்திகளை மேம்படுத்தவும் அவசியம்.
த்ரெட்களுக்கு அப்பால்: மல்டிபிராசசிங் மற்றும் ஒத்திசைவற்ற I/O
GIL காரணமாக I/O-பவுண்ட் பணிகளுக்கு த்ரெட்கள் சிறந்தவை என்றாலும், அவை பைத்தானில் உண்மையான CPU பேரலலிசத்தை வழங்காது. CPU-பவுண்ட் பணிகளுக்கு (எ.கா., கனமான எண் கணக்கீடு, பட செயலாக்கம், சிக்கலான தரவு பகுப்பாய்வு), multiprocessing என்பது செல்ல வேண்டிய தீர்வாகும். multiprocessing தொகுதி தனித்தனி செயல்முறைகளை உருவாக்குகிறது, ஒவ்வொன்றும் அதன் சொந்த பைதான் இன்டர்பிரிட்டர் மற்றும் நினைவக இடத்துடன், GIL ஐ திறம்பட தவிர்த்து, பல CPU கோர்களில் உண்மையான இணையான செயல்பாட்டை அனுமதிக்கிறது. செயல்முறைகளுக்கு இடையேயான தொடர்பு பொதுவாக multiprocessing.Queue (இது threading.Queue ஐப் போன்றது ஆனால் செயல்முறைகளுக்காக வடிவமைக்கப்பட்டது), பைப்ஸ் அல்லது பகிரப்பட்ட நினைவகம் போன்ற சிறப்பு இடை-செயல்முறை தொடர்பு (IPC) வழிமுறைகளைப் பயன்படுத்துகிறது.
த்ரெட்களின் மேல்நிலை அல்லது லாக்குகளின் சிக்கல்கள் இல்லாமல் அதிக திறமையான I/O-பவுண்ட் கன்கரன்சிக்கு, பைதான் ஒத்திசைவற்ற I/O க்காக asyncio ஐ வழங்குகிறது. asyncio பல கன்கரன்ட் I/O செயல்பாடுகளை நிர்வகிக்க ஒரு ஒற்றை-த்ரெட்டட் நிகழ்வு வளையத்தைப் பயன்படுத்துகிறது. தடுப்பதற்குப் பதிலாக, செயல்பாடுகள் I/O செயல்பாடுகளை "await" செய்கின்றன, மற்ற பணிகள் இயங்குவதற்காக கட்டுப்பாட்டை நிகழ்வு வளையத்திற்குத் திரும்ப அளிக்கின்றன. இந்த மாதிரி நெட்வொர்க்-கனமான பயன்பாடுகளுக்கு மிகவும் திறமையானது, வலை சேவையகங்கள் அல்லது நிகழ்நேர தரவு ஸ்ட்ரீமிங் சேவைகள் போன்றவை, உலகளாவிய வரிசைப்படுத்தல்களில் பொதுவானவை, அங்கு ஆயிரக்கணக்கான அல்லது மில்லியன் கணக்கான கன்கரன்ட் இணைப்புகளை நிர்வகிப்பது முக்கியமானது.
threading, multiprocessing, மற்றும் asyncio ஆகியவற்றின் பலம் மற்றும் பலவீனங்களைப் புரிந்துகொள்வது மிகவும் பயனுள்ள கன்கரன்சி உத்தியை வடிவமைப்பதற்கு முக்கியமானது. ஒரு கலப்பின அணுகுமுறை, CPU-தீவிர கணக்கீடுகளுக்கு multiprocessing ஐப் பயன்படுத்துதல் மற்றும் I/O-தீவிர பகுதிகளுக்கு threading அல்லது asyncio ஐப் பயன்படுத்துதல், பெரும்பாலும் சிக்கலான, உலகளவில் வரிசைப்படுத்தப்பட்ட பயன்பாடுகளுக்கு சிறந்த செயல்திறனை அளிக்கிறது. உதாரணமாக, ஒரு வலை சேவை பல்வேறு வாடிக்கையாளர்களிடமிருந்து வரும் கோரிக்கைகளைக் கையாள asyncio ஐப் பயன்படுத்தலாம், பின்னர் CPU-பவுண்ட் பகுப்பாய்வு பணிகளை ஒரு multiprocessing குளத்திற்கு அனுப்பலாம், அது பின்னர் பல வெளிப்புற API களில் இருந்து துணைத் தரவைப் பெற threading ஐப் பயன்படுத்தலாம்.
வலுவான கன்கரன்ட் பைதான் பயன்பாடுகளை உருவாக்குவதற்கான சிறந்த நடைமுறைகள்
செயல்திறன்மிக்க, நம்பகமான மற்றும் பராமரிக்கக்கூடிய கன்கரன்ட் பயன்பாடுகளை உருவாக்குவதற்கு ஒரு சிறந்த நடைமுறைகளின் தொகுப்பைப் பின்பற்றுவது தேவைப்படுகிறது. இவை எந்தவொரு டெவலப்பருக்கும் முக்கியமானவை, குறிப்பாக பல்வேறு சூழல்களில் செயல்படும் மற்றும் உலகளாவிய பயனர் தளத்திற்கு சேவை செய்யும் அமைப்புகளை வடிவமைக்கும்போது.
- முக்கியமான பிரிவுகளை முன்கூட்டியே அடையாளம் காணவும்: எந்தவொரு கன்கரன்ட் குறியீட்டையும் எழுதுவதற்கு முன்பு, அனைத்து பகிரப்பட்ட வளங்களையும் அவற்றை மாற்றும் குறியீட்டின் முக்கியமான பிரிவுகளையும் அடையாளம் காணவும். இது ஒத்திசைவு எங்கு தேவை என்பதைத் தீர்மானிப்பதற்கான முதல் படியாகும்.
- சரியான ஒத்திசைவு பிரிமிட்டிவைத் தேர்ந்தெடுக்கவும்:
Lock,RLock,Semaphore,Event, மற்றும்Conditionஆகியவற்றின் நோக்கத்தைப் புரிந்து கொள்ளுங்கள். ஒருSemaphoreமிகவும் பொருத்தமான இடத்தில் ஒருLockஐப் பயன்படுத்த வேண்டாம், அல்லது நேர்மாறாக. எளிய தயாரிப்பாளர்-நுகர்வோருக்கு,queueதொகுதிக்கு முன்னுரிமை அளியுங்கள். - லாக் பிடிப்பு நேரத்தைக் குறைக்கவும்: ஒரு முக்கியமான பிரிவில் நுழைவதற்கு சற்று முன்பு லாக்குகளைப் பெற்று, முடிந்தவரை விரைவில் அவற்றை விடுவிக்கவும். தேவையை விட நீண்ட நேரம் லாக்குகளைப் பிடிப்பது போட்டியை அதிகரித்து, பேரலலிசம் அல்லது கன்கரன்சியின் அளவைக் குறைக்கிறது. ஒரு லாக்கைப் பிடித்துக்கொண்டு I/O செயல்பாடுகள் அல்லது நீண்ட கணக்கீடுகளைச் செய்வதைத் தவிர்க்கவும்.
- நெஸ்டட் லாக்குகளைத் தவிர்க்கவும் அல்லது நிலையான வரிசைமுறையைப் பயன்படுத்தவும்: நீங்கள் பல லாக்குகளைப் பயன்படுத்த வேண்டியிருந்தால், டெட்லாக்குகளைத் தடுக்க அனைத்து த்ரெட்களிலும் எப்போதும் ஒரு முன்னரே வரையறுக்கப்பட்ட, நிலையான வரிசையில் அவற்றைப் பெறவும். அதே த்ரெட் ஒரு லாக்கை மீண்டும் சட்டப்பூர்வமாகப் பெறக்கூடும் என்றால்
RLockஐப் பயன்படுத்துவதைக் கருத்தில் கொள்ளுங்கள். - உயர்-நிலை சுருக்கங்களைப் பயன்படுத்தவும்: முடிந்தவரை,
queueதொகுதி வழங்கும் த்ரெட்-பாதுகாப்பான தரவுக் கட்டமைப்புகளைப் பயன்படுத்தவும். இவை முழுமையாக சோதிக்கப்பட்டவை, மேம்படுத்தப்பட்டவை, மற்றும் கைமுறை லாக் நிர்வாகத்துடன் ஒப்பிடும்போது அறிவாற்றல் சுமை மற்றும் பிழை மேற்பரப்பைக் கணிசமாகக் குறைக்கின்றன. - கன்கரன்சியின் கீழ் முழுமையாக சோதிக்கவும்: கன்கரன்ட் பிழைகளை மீண்டும் உருவாக்குவதும் சரிசெய்வதும் மிகவும் கடினம். அதிக கன்கரன்சியை உருவகப்படுத்தும் மற்றும் உங்கள் ஒத்திசைவு வழிமுறைகளை அழுத்தும் முழுமையான யூனிட் மற்றும் ஒருங்கிணைப்பு சோதனைகளைச் செயல்படுத்தவும்.
pytest-asyncioபோன்ற கருவிகள் அல்லது தனிப்பயன் சுமை சோதனைகள் விலைமதிப்பற்றதாக இருக்கலாம். - கன்கரன்சி அனுமானங்களை ஆவணப்படுத்தவும்: உங்கள் குறியீட்டின் எந்தப் பகுதிகள் த்ரெட்-பாதுகாப்பானவை, எவை அல்ல, மற்றும் என்ன ஒத்திசைவு வழிமுறைகள் உள்ளன என்பதைத் தெளிவாக ஆவணப்படுத்தவும். இது எதிர்கால பராமரிப்பாளர்கள் கன்கரன்சி மாதிரியைப் புரிந்துகொள்ள உதவுகிறது.
- உலகளாவிய தாக்கம் மற்றும் விநியோகிக்கப்பட்ட நிலைத்தன்மையைக் கருத்தில் கொள்ளுங்கள்: உலகளாவிய வரிசைப்படுத்தல்களுக்கு, தாமதம் மற்றும் நெட்வொர்க் பகிர்வுகள் உண்மையான சவால்கள். செயல்முறை-நிலை கன்கரன்சிக்கு அப்பால், விநியோகிக்கப்பட்ட அமைப்புகள் பேட்டர்ன்ஸ், இறுதி நிலைத்தன்மை, மற்றும் தரவு மையங்கள் அல்லது பிராந்தியங்களுக்கு இடையில் சேவை-இடைப்பட்ட தொடர்புக்காக காஃப்கா அல்லது ராபிட்எம்க்யூ போன்ற செய்தி வரிசைகளைப் பற்றி சிந்தியுங்கள்.
- மாறாத தன்மையை விரும்புங்கள்: மாறாத தரவுக் கட்டமைப்புகள் இயல்பாகவே த்ரெட்-பாதுகாப்பானவை, ஏனெனில் அவை உருவாக்கப்பட்ட பிறகு மாற்றப்பட முடியாது, இது லாக்குகளின் தேவையை நீக்குகிறது. இது எப்போதும் சாத்தியமில்லை என்றாலும், முடிந்தவரை மாறாத தரவைப் பயன்படுத்த உங்கள் அமைப்பின் பகுதிகளை வடிவமைக்கவும்.
- சுயவிவரம் மற்றும் மேம்படுத்துதல்: உங்கள் கன்கரன்ட் பயன்பாடுகளில் செயல்திறன் இடையூறுகளை அடையாளம் காண சுயவிவரக் கருவிகளைப் பயன்படுத்தவும். முன்கூட்டியே மேம்படுத்த வேண்டாம்; முதலில் அளவிடவும், பின்னர் அதிக போட்டி உள்ள பகுதிகளை குறிவைக்கவும்.
முடிவுரை: ஒரு கன்கரன்ட் உலகத்திற்காக பொறியியல்
கன்கரன்சியை திறம்பட நிர்வகிக்கும் திறன் இனி ஒரு முக்கியத் திறன் அல்ல, ஆனால் ஒரு உலகளாவிய பயனர் தளத்திற்கு சேவை செய்யும் நவீன, உயர் செயல்திறன் கொண்ட பயன்பாடுகளை உருவாக்குவதற்கான ஒரு அடிப்படைத் தேவையாகும். பைதான், அதன் GIL இருந்தபோதிலும், அதன் threading தொகுதிக்குள் வலுவான, த்ரெட்-பாதுகாப்பான தரவுக் கட்டமைப்புகளை உருவாக்க சக்திவாய்ந்த கருவிகளை வழங்குகிறது, இது டெவலப்பர்கள் பகிரப்பட்ட நிலை மற்றும் ரேஸ் கண்டிஷன்களின் சவால்களை சமாளிக்க உதவுகிறது. முக்கிய ஒத்திசைவு பிரிமிட்டிவ்களை – லாக்குகள், செமாஃபோர்கள், நிகழ்வுகள், மற்றும் நிபந்தனைகள் – புரிந்துகொண்டு, த்ரெட்-பாதுகாப்பான பட்டியல்கள், க்யூக்கள், கவுண்டர்கள் மற்றும் கேஷ்களை உருவாக்குவதில் அவற்றின் பயன்பாட்டில் தேர்ச்சி பெறுவதன் மூலம், நீங்கள் அதிக சுமையின் கீழ் தரவு நேர்மை மற்றும் பதிலளிக்கக்கூடிய தன்மையைப் பராமரிக்கும் அமைப்புகளை வடிவமைக்க முடியும்.
அதிகரித்து வரும் இணைக்கப்பட்ட உலகிற்கான பயன்பாடுகளை நீங்கள் கட்டமைக்கும்போது, வெவ்வேறு கன்கரன்சி மாதிரிகளுக்கு இடையேயான பரிமாற்றங்களை கவனமாகக் கருத்தில் கொள்ள நினைவில் கொள்ளுங்கள், அது பைத்தானின் நேட்டிவ் threading ஆக இருந்தாலும், உண்மையான பேரலலிசத்திற்கு multiprocessing ஆக இருந்தாலும், அல்லது திறமையான I/O க்காக asyncio ஆக இருந்தாலும். தெளிவான வடிவமைப்பு, முழுமையான சோதனை, மற்றும் சிறந்த நடைமுறைகளைப் பின்பற்றுவதற்கு முன்னுரிமை அளியுங்கள், கன்கரன்ட் புரோகிராமிங்கின் சிக்கல்களுக்கு செல்ல. இந்த பேட்டர்ன்ஸ் மற்றும் கொள்கைகள் உறுதியாகக் கையில் இருப்பதால், நீங்கள் எந்தவொரு உலகளாவிய தேவைக்கும் சக்திவாய்ந்த மற்றும் திறமையானது மட்டுமல்லாமல், நம்பகமான மற்றும் அளவிடக்கூடிய பைதான் தீர்வுகளை பொறியியல் செய்ய நன்கு தயாராக உள்ளீர்கள். கன்கரன்ட் மென்பொருள் மேம்பாட்டின் எப்போதும் மாறிவரும் நிலப்பரப்பிற்கு தொடர்ந்து கற்றுக்கொள்ளுங்கள், பரிசோதனை செய்யுங்கள் மற்றும் பங்களிக்கவும்.