Pythonda `concurrent.futures` moduli bo'yicha keng qo'llanma. `ThreadPoolExecutor` va `ProcessPoolExecutor` parallel vazifalarni bajarishda solishtiriladi.
Pythonda Konkurentlikni ochish: ThreadPoolExecutor va ProcessPoolExecutor
Python ko'p qirrali va keng qo'llaniladigan dasturlash tili bo'lsa-da, Global Interpreter Lock (GIL) tufayli haqiqiy parallellik borasida ba'zi cheklovlarga ega. The concurrent.futures
moduli chaqiriladigan funksiyalarni asinxron tarzda bajarish uchun yuqori darajadagi interfeysni ta'minlaydi, bu esa ba'zi cheklovlarni chetlab o'tish va vazifalarning ayrim turlari uchun ishlash samaradorligini oshirish imkonini beradi. Ushbu modul ikkita asosiy sinfni taqdim etadi: ThreadPoolExecutor
va ProcessPoolExecutor
. Ushbu keng qamrovli qo'llanma ularning ikkalasini ham o'rganib chiqadi, farqlari, kuchli va zaif tomonlarini ta'kidlaydi hamda ehtiyojlaringiz uchun to'g'ri ijrochani tanlashga yordam beradigan amaliy misollar keltiriladi.
Konkurentlik va Parallellikni tushunish
Har bir ijrochining o'ziga xos xususiyatlariga kirishdan oldin, konkurentlik va parallellik tushunchalarini tushunish muhimdir. Bu atamalar ko'pincha bir-birining o'rnida ishlatiladi, ammo ularning aniq ma'nolari bor:
- Konkurentlik: Bir vaqtning o'zida bir nechta vazifalarni boshqarish bilan shug'ullanadi. Bu sizning kodingizni bir nechta narsalarni bir xil vaqtda bajarayotgandek ko'rinish uchun tuzishdir, garchi ular aslida bitta protsessor yadrosida navbatma-navbat bajarilsa ham. Buni bitta plitada bir nechta qozonni boshqarayotgan oshpazga o'xshatish mumkin – ular bir vaqtning aniq o'zida qaynayotgan bo'lmasa-da, oshpaz ularning hammasini boshqaradi.
- Parallellik: Bir vaqtning o'zida bir nechta vazifalarni bajarishni o'z ichiga oladi, odatda bir nechta protsessor yadrolaridan foydalangan holda. Bu bir nechta oshpazlar bo'lib, ularning har biri ovqatning turli qismlarini bir vaqtda tayyorlashga o'xshaydi.
Pythonning GIL'i odatda, thread'lardan foydalanganda CPUga bog'liq bo'lgan vazifalar uchun haqiqiy parallellikni cheklaydi. Buning sababi, GIL bir vaqtning o'zida faqat bitta thread'ga Python interpreterini boshqarishga ruxsat beradi. Biroq, I/Oga bog'liq bo'lgan vazifalar uchun, bunda dastur ko'p vaqtini tarmoq so'rovlari yoki diskdan o'qish kabi tashqi operatsiyalarni kutishga sarflaydi, thread'lar kutayotgan paytda boshqa thread'larga ishlashga ruxsat berish orqali sezilarli ishlash samaradorligini ta'minlashi mumkin.
`concurrent.futures` Moduli bilan tanishish
concurrent.futures
moduli vazifalarni asinxron bajarish jarayonini soddalashtiradi. U thread'lar va process'lar bilan ishlash uchun yuqori darajadagi interfeysni taqdim etadi, ularni to'g'ridan-to'g'ri boshqarish bilan bog'liq murakkabliklarning ko'p qismini yashiradi. Asosiy tushuncha - bu "executor" bo'lib, u yuborilgan vazifalarning bajarilishini boshqaradi. Ikkita asosiy executor mavjud:
ThreadPoolExecutor
: Vazifalarni bajarish uchun thread'lar pulidan foydalanadi. I/Oga bog'liq vazifalar uchun mos keladi.ProcessPoolExecutor
: Vazifalarni bajarish uchun process'lar pulidan foydalanadi. CPUga bog'liq vazifalar uchun mos keladi.
ThreadPoolExecutor: I/Oga bog'liq vazifalar uchun thread'lardan foydalanish
ThreadPoolExecutor
vazifalarni bajarish uchun ishchi thread'lar pulini yaratadi. GIL tufayli, thread'lar haqiqiy parallellikdan foyda ko'radigan hisoblash talab qiluvchi operatsiyalar uchun ideal emas. Biroq, ular I/Oga bog'liq bo'lgan stsenariylarda juda yaxshi ishlaydi. Keling, undan qanday foydalanishni ko'rib chiqamiz:
Asosiy foydalanish
Bir nechta veb-sahifalarni bir vaqtning o'zida yuklab olish uchun ThreadPoolExecutor
'dan foydalanishning oddiy misoli:
import concurrent.futures
import requests
import time
urls = [
"https://www.example.com",
"https://www.google.com",
"https://www.wikipedia.org",
"https://www.python.org"
]
def download_page(url):
try:
response = requests.get(url, timeout=5)
response.raise_for_status() # Raise HTTPError for bad responses (4xx or 5xx)
print(f"Downloaded {url}: {len(response.content)} bytes")
return len(response.content)
except requests.exceptions.RequestException as e:
print(f"Error downloading {url}: {e}")
return 0
start_time = time.time()
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
# Submit each URL to the executor
futures = [executor.submit(download_page, url) for url in urls]
# Wait for all tasks to complete
total_bytes = sum(future.result() for future in concurrent.futures.as_completed(futures))
print(f"Total bytes downloaded: {total_bytes}")
print(f"Time taken: {time.time() - start_time:.2f} seconds")
Tushuntirish:
- Biz kerakli modullarni import qilamiz:
concurrent.futures
,requests
vatime
. - Biz yuklab olish uchun URL manzillari ro'yxatini belgilaymiz.
- The
download_page
funksiyasi berilgan URL manzilining mazmunini olib keladi. Tarmoq bilan bog'liq muammolarni bartaraf etish uchun `try...except` va `response.raise_for_status()` yordamida xatolarni boshqarish kiritilgan. - Biz maksimal 4 ta ishchi thread'ga ega
ThreadPoolExecutor
yaratamiz. Themax_workers
argumenti bir vaqtning o'zida foydalanish mumkin bo'lgan thread'lar sonini nazorat qiladi. Uni juda yuqori qilib o'rnatish har doim ham ishlash samaradorligini oshirmaydi, ayniqsa tarmoq o'tkazish qobiliyati ko'pincha cheklovchi omil bo'lgan I/Oga bog'liq vazifalarda. - Biz har bir URL manzilini
executor.submit(download_page, url)
yordamida ijrochiga yuborish uchun list comprehension'dan foydalanamiz. Bu har bir vazifa uchunFuture
obyektini qaytaradi. - The
concurrent.futures.as_completed(futures)
funksiyasi vazifalar tugallanishi bilan future'larni qaytaruvchi iteratorni qaytaradi. Bu natijalarni qayta ishlashdan oldin barcha vazifalar tugashini kutishni oldini oladi. - Biz tugallangan future'lar bo'ylab iteratsiya qilamiz va har bir vazifaning natijasini
future.result()
yordamida olamiz, yuklab olingan umumiy baytlarni jamlaymiz. `download_page` ichidagi xatolarni boshqarish alohida xatolar butun jarayonni buzmasligini ta'minlaydi. - Nihoyat, biz yuklab olingan umumiy baytlarni va ketgan vaqtni chop etamiz.
ThreadPoolExecutor'ning afzalliklari
- Soddalashtirilgan konkurentlik: Thread'larni boshqarish uchun toza va foydalanishga qulay interfeysni ta'minlaydi.
- I/Oga bog'liq vazifalarda ishlash samaradorligi: Tarmoq so'rovlari, fayllarni o'qish yoki ma'lumotlar bazasi so'rovlari kabi I/O operatsiyalarini kutishga ko'p vaqt sarflaydigan vazifalar uchun juda mos keladi.
- Kamaytirilgan yuklanish: Thread'lar odatda process'larga nisbatan kamroq yuklanishga ega bo'lib, ular tez-tez kontekst almashishni o'z ichiga olgan vazifalar uchun samaraliroqdir.
ThreadPoolExecutor'ning cheklovlari
- GIL cheklovi: GIL CPUga bog'liq vazifalar uchun haqiqiy parallellikni cheklaydi. Bir vaqtning o'zida faqat bitta thread Python baytkodini bajarishi mumkin, bu esa bir nechta yadro afzalliklarini yo'qqa chiqaradi.
- Disk raskadrovka murakkabligi: Multithread ilovalarni disk raskadrovka qilish race condition'lar va boshqa konkurentlik bilan bog'liq muammolar tufayli qiyin bo'lishi mumkin.
ProcessPoolExecutor: CPUga bog'liq vazifalar uchun Multiprocessing imkoniyatlarini ishga tushirish
ProcessPoolExecutor
ishchi process'lar pulini yaratish orqali GIL cheklovini bartaraf etadi. Har bir process o'zining Python interpretatoriga va xotira maydoniga ega bo'lib, ko'p yadroli tizimlarda haqiqiy parallellikni ta'minlaydi. Bu uni og'ir hisoblashlarni o'z ichiga olgan CPUga bog'liq vazifalar uchun ideal qiladi.
Asosiy foydalanish
Keling, katta diapazondagi sonlarning kvadratlari yig'indisini hisoblash kabi hisoblash talab qiluvchi vazifani ko'rib chiqaylik. Ushbu vazifani parallel bajarish uchun ProcessPoolExecutor
'dan qanday foydalanish mumkin:
import concurrent.futures
import time
import os
def sum_of_squares(start, end):
pid = os.getpid()
print(f"Process ID: {pid}, Calculating sum of squares from {start} to {end}")
total = 0
for i in range(start, end + 1):
total += i * i
return total
if __name__ == "__main__": #Important for avoiding recursive spawning in some environments
start_time = time.time()
range_size = 1000000
num_processes = 4
ranges = [(i * range_size + 1, (i + 1) * range_size) for i in range(num_processes)]
with concurrent.futures.ProcessPoolExecutor(max_workers=num_processes) as executor:
futures = [executor.submit(sum_of_squares, start, end) for start, end in ranges]
results = [future.result() for future in concurrent.futures.as_completed(futures)]
total_sum = sum(results)
print(f"Total sum of squares: {total_sum}")
print(f"Time taken: {time.time() - start_time:.2f} seconds")
Tushuntirish:
- Biz berilgan sonlar diapazoni uchun kvadratlar yig'indisini hisoblaydigan
sum_of_squares
funksiyasini belgilaymiz. Biz `os.getpid()`'ni kiritamiz, qaysi process har bir diapazonni bajarayotganini ko'rish uchun. - Biz diapazon hajmini va foydalaniladigan process'lar sonini belgilaymiz. The
ranges
ro'yxati umumiy hisoblash diapazonini har bir process uchun kichikroq bo'laklarga bo'lish uchun yaratilgan. - Biz belgilangan ishchi process'lar soniga ega
ProcessPoolExecutor
yaratamiz. - Biz har bir diapazonni
executor.submit(sum_of_squares, start, end)
yordamida ijrochiga yuboramiz. - Biz har bir future'dan natijalarni
future.result()
yordamida yig'amiz. - Biz yakuniy umumiy natijani olish uchun barcha process'lardan olingan natijalarni jamlaymiz.
Muhim eslatma: ProcessPoolExecutor
'dan foydalanganda, ayniqsa Windows'da, ijrochining yaratilish kodini if __name__ == "__main__":
bloki ichiga olish kerak. Bu rekursiv process paydo bo'lishining oldini oladi, bu esa xatolarga va kutilmagan xatti-harakatlarga olib kelishi mumkin. Buning sababi shundaki, modul har bir child process'da qayta import qilinadi.
ProcessPoolExecutor'ning afzalliklari
- Haqiqiy parallellik: GIL cheklovini bartaraf etadi, CPUga bog'liq vazifalar uchun ko'p yadroli tizimlarda haqiqiy parallellikka imkon beradi.
- CPUga bog'liq vazifalarda yaxshilangan ishlash samaradorligi: Hisoblash talab qiluvchi operatsiyalar uchun sezilarli ishlash samaradorligi oshishiga erishish mumkin.
- Mustahkamlik: Agar bitta process ishdan chiqsa, bu butun dasturni yiqitishga olib kelmaydi, chunki process'lar bir-biridan izolyatsiya qilingan.
ProcessPoolExecutor'ning cheklovlari
- Yuqori yuklanish: Process'larni yaratish va boshqarish thread'larga nisbatan yuqoriroq yuklanishga ega.
- Jarayonlararo aloqa: Process'lar o'rtasida ma'lumot almashish murakkabroq bo'lishi mumkin va jarayonlararo aloqa (IPC) mexanizmlarini talab qiladi, bu esa qo'shimcha yuklanishga olib kelishi mumkin.
- Xotira izi: Har bir process o'zining xotira maydoniga ega bo'lib, bu ilovaning umumiy xotira izini oshirishi mumkin. Process'lar o'rtasida katta hajmdagi ma'lumotlarni uzatish cheklovchi omilga aylanishi mumkin.
To'g'ri ijrochani tanlash: ThreadPoolExecutor va ProcessPoolExecutor
ThreadPoolExecutor
va ProcessPoolExecutor
o'rtasida tanlov qilishning kaliti sizning vazifalaringizning tabiatini tushunishda yotadi:
- I/Oga bog'liq vazifalar: Agar sizning vazifalaringiz ko'p vaqtini I/O operatsiyalarini kutishga sarflasa (masalan, tarmoq so'rovlari, fayllarni o'qish, ma'lumotlar bazasi so'rovlari), odatda
ThreadPoolExecutor
yaxshiroq tanlovdir. GIL unchalik cheklovchi omil emas bunday stsenariylarda va thread'larning kamroq yuklanishi ularni samaraliroq qiladi. - CPUga bog'liq vazifalar: Agar sizning vazifalaringiz hisoblash talab qiluvchi va bir nechta yadrolardan foydalanayotgan bo'lsa,
ProcessPoolExecutor
to'g'ri yo'ldir. U GIL cheklovini chetlab o'tadi va haqiqiy parallellikka imkon beradi, natijada ishlash samaradorligini sezilarli darajada oshiradi.
Quyida asosiy farqlarni umumlashtiruvchi jadval keltirilgan:
Xususiyat | ThreadPoolExecutor | ProcessPoolExecutor |
---|---|---|
Konkurentlik modeli | Multithreading | Multiprocessing |
GIL ta'siri | GIL bilan cheklangan | GILni chetlab o'tadi |
Mos keladi | I/Oga bog'liq vazifalar | CPUga bog'liq vazifalar |
Yuklanish | Past | Yuqori |
Xotira izi | Past | Yuqori |
Jarayonlararo aloqa | Talab qilinmaydi (thread'lar xotirani bo'lishadi) | Ma'lumot almashish uchun talab qilinadi |
Mustahkamlik | Kamroq mustahkam (xato butun jarayonga ta'sir qilishi mumkin) | Ko'proq mustahkam (process'lar izolyatsiya qilingan) |
Kengaytirilgan usullar va mulohazalar
Argumentlar bilan vazifalarni yuborish
Ikkala executor ham bajarilayotgan funksiyaga argumentlarni uzatish imkonini beradi. Bu submit()
metodi orqali amalga oshiriladi:
with concurrent.futures.ThreadPoolExecutor() as executor:
future = executor.submit(my_function, arg1, arg2)
result = future.result()
Istisnolarni boshqarish
Bajarilayotgan funksiya ichida yuzaga kelgan istisnolar asosiy thread'ga yoki process'ga avtomatik ravishda tarqalmaydi. Ularni Future
natijasini olayotganda aniq boshqarishingiz kerak:
with concurrent.futures.ThreadPoolExecutor() as executor:
future = executor.submit(my_function)
try:
result = future.result()
except Exception as e:
print(f"An exception occurred: {e}")
Oddiy vazifalar uchun `map`'dan foydalanish
Bir qator kiritishlarga bir xil funksiyani qo'llashni istagan oddiy vazifalar uchun, map()
metodi vazifalarni yuborishning ixcham usulini taqdim etadi:
def square(x):
return x * x
with concurrent.futures.ProcessPoolExecutor() as executor:
numbers = [1, 2, 3, 4, 5]
results = executor.map(square, numbers)
print(list(results))
Ishchilar sonini nazorat qilish
ThreadPoolExecutor
va ProcessPoolExecutor
'dagi max_workers
argumenti bir vaqtning o'zida ishlatilishi mumkin bo'lgan thread'lar yoki process'larning maksimal sonini nazorat qiladi. max_workers
uchun to'g'ri qiymatni tanlash ishlash samaradorligi uchun muhimdir. Yaxshi boshlang'ich nuqta sizning tizimingizda mavjud bo'lgan CPU yadrolari sonidir. Biroq, I/Oga bog'liq vazifalar uchun, siz yadrolardan ko'proq thread'lardan foydalanishdan foyda olishingiz mumkin, chunki thread'lar I/O kutayotgan paytda boshqa vazifalarga o'tishi mumkin. Optimal qiymatni aniqlash uchun ko'pincha tajriba va profiling qilish kerak bo'ladi.
Jarayonni kuzatish
The concurrent.futures
moduli vazifalarning jarayonini to'g'ridan-to'g'ri kuzatish uchun o'rnatilgan mexanizmlarni ta'minlamaydi. Biroq, siz callback'lar yoki umumiy o'zgaruvchilardan foydalangan holda o'zingizning jarayonni kuzatish tizimingizni amalga oshirishingiz mumkin. `tqdm` kabi kutubxonalar jarayon panellarini ko'rsatish uchun integratsiya qilinishi mumkin.
Haqiqiy dunyo misollari
Keling, ThreadPoolExecutor
va ProcessPoolExecutor
samarali qo'llanilishi mumkin bo'lgan ba'zi haqiqiy dunyo stsenariylarini ko'rib chiqaylik:
- Veb-scraping: Bir nechta veb-sahifalarni
ThreadPoolExecutor
yordamida bir vaqtning o'zida yuklab olish va tahlil qilish. Har bir thread boshqa veb-sahifani boshqarishi mumkin, bu umumiy scraping tezligini oshiradi. Veb-sayt xizmat ko'rsatish shartlariga e'tibor bering va ularning serverlarini ortiqcha yuklashdan saqlaning. - Rasmlarni qayta ishlash: Katta hajmdagi rasmlarga
ProcessPoolExecutor
yordamida rasm filtrlari yoki transformatsiyalarini qo'llash. Har bir process boshqa rasmni qayta ishlashi mumkin, bu tezroq ishlov berish uchun bir nechta yadrolardan foydalanadi. Samarali rasmlarni manipulyatsiya qilish uchun OpenCV kabi kutubxonalarni ko'rib chiqing. - Ma'lumotlar tahlili: Katta ma'lumotlar to'plamida
ProcessPoolExecutor
yordamida murakkab hisob-kitoblarni bajarish. Har bir process ma'lumotlarning bir qismini tahlil qilishi mumkin, bu umumiy tahlil vaqtini qisqartiradi. Pandas va NumPy Python'da ma'lumotlar tahlili uchun mashhur kutubxonalardir. - Mashinaviy ta'lim:
ProcessPoolExecutor
yordamida mashinaviy ta'lim modellarini o'qitish. Ba'zi mashinaviy ta'lim algoritmlari samarali ravishda parallel bajarilishi mumkin, bu tezroq o'qitish vaqtlarini ta'minlaydi. Scikit-learn va TensorFlow kabi kutubxonalar parallellikni qo'llab-quvvatlaydi. - Videoni kodlash: Video fayllarni
ProcessPoolExecutor
yordamida turli formatlarga aylantirish. Har bir process videoning boshqa segmentini kodlashi mumkin, bu umumiy kodlash jarayonini tezlashtiradi.
Global mulohazalar
Global auditoriya uchun konkurent ilovalar ishlab chiqishda quyidagilarni hisobga olish muhimdir:
- Vaqt zonasi: Vaqtga sezgir operatsiyalar bilan ishlashda vaqt zonalariga e'tibor bering. Vaqt zonasi konvertatsiyalarini boshqarish uchun
pytz
kabi kutubxonalardan foydalaning. - Lokallar: Ilovaning turli lokallarni to'g'ri boshqarishini ta'minlang. Foydalanuvchining lokaliga ko'ra raqamlar, sanalar va valyutalarni formatlash uchun
locale
kabi kutubxonalardan foydalaning. - Belgilar kodlashlari: Keng doiradagi tillarni qo'llab-quvvatlash uchun standart belgilar kodlash sifatida Unicode (UTF-8) dan foydalaning.
- Xalqaroizatsiyalash (i18n) va Lokallash (l10n): Ilovangizni osonlikcha xalqaroizatsiyalash va lokallash uchun ishlab chiqing. Turli tillar uchun tarjimalarni ta'minlash uchun gettext yoki boshqa tarjima kutubxonalaridan foydalaning.
- Tarmoq kechikishi: Uzoq xizmatlar bilan aloqa qilishda tarmoq kechikishini hisobga oling. Ilovaning tarmoq muammolariga chidamli bo'lishini ta'minlash uchun tegishli vaqt chegaralari va xatolarni boshqarishni amalga oshiring. Serverlarning geografik joylashuvi kechikishga sezilarli ta'sir ko'rsatishi mumkin. Turli mintaqalardagi foydalanuvchilar uchun ishlash samaradorligini oshirish uchun Kontent Yetkazib Berish Tarmoqlari (CDN) dan foydalanishni ko'rib chiqing.
Xulosa
concurrent.futures
moduli Python ilovalaringizga konkurentlik va parallellikni kiritishning kuchli va qulay usulini ta'minlaydi. ThreadPoolExecutor
va ProcessPoolExecutor
o'rtasidagi farqlarni tushunib, vazifalaringizning tabiatini diqqat bilan ko'rib chiqish orqali siz kodingizning ishlash samaradorligini va javob berish tezligini sezilarli darajada oshirishingiz mumkin. Kodingizni profiling qilib, o'zingizning aniq foydalanish holatingiz uchun optimal sozlamalarni topish maqsadida turli konfiguratsiyalar bilan tajriba o'tkazishni unutmang. Shuningdek, GIL cheklovlari va multithreaded hamda multiprocessing dasturlashning mumkin bo'lgan murakkabliklaridan xabardor bo'ling. Sinchkov rejalashtirish va amalga oshirish bilan siz Python'da konkurentlikning to'liq salohiyatini ochishingiz va global auditoriya uchun mustahkam va kengaytiriladigan ilovalar yaratishingiz mumkin.