Barcha qo'llanmalar

Python multithreading: eng amaliy kirish

Python'dagi multithreading va GIL sizni chalkashtiryaptimi? I/O vazifalari uchun thread'lardan amaliy foydalanish, race condition'larning oldini olish va dastur sezgirligini oshirish usullarini o'rganing.

Muallif: Ekene Eze

Python'da multithreading boshqa ko'plab tillardagidan farqli ishlaydi, bunga asosan Global Interpreter Lock (GIL) sabab bo'ladi. Python CPU-intensive ishlar uchun bir nechta thread'ni parallel ishga tushirmasa-da, vaqtining ko'p qismini kutish bilan o'tkazadigan vazifalar — masalan, tarmoq chaqiruvlari, fayl operatsiyalari va ma'lumotlar bazasi query'lari uchun u baribir sezilarli afzalliklar beradi.

Python thread'larni qanday boshqarishini tushunish bir vaqtning o'zida ko'plab I/O operatsiyalarini bajarayotganda ham tez va sezgir bo'lib qoladigan dasturlar yozishga yordam beradi.

Ushbu qo'llanmada siz:

  • Multithreaded fayl yuklab oluvchi dasturni qanday yaratishni tushunasiz

  • Thread'larni xavfsiz boshqarishni o'rganasiz

  • Race condition va deadlock'larni bartaraf etasiz

  • Threading'ni multiprocessing va async bilan solishtirasiz

  • Daemon thread'lar, producer-consumer va context manager'larni ko'rib chiqasiz

  • Yo'naltiruvchi topshiriqlar va AI Tutor prompt'lari bilan mashq qilasiz

Chuqurroq kirishdan oldin o'zingizga shunday savol berish foydali: umuman olganda, multithreading nima uchun mavjud? U hal qilish uchun yaratilgan dastlabki muammoni tushunish uning Python'dagi o'rnini ancha oydinlashtiradi.

Python multithreading nima?

Python multithreading — bitta dastur ichida bir nechta thread'ni ishga tushirish usuli bo'lib, u turli vazifalarga bir-birini bloklamasdan oldinga siljish imkonini beradi. Global Interpreter Lock (GIL) thread'larning Python bytecode'ini haqiqiy parallel tarzda bajarishiga yo'l qo'ymasa-da, vaqtining ko'p qismini kutish bilan o'tkazadigan operatsiyalar uchun thread'lar juda qimmatli bo'lib qolaveradi.

Thread to'xtab turganida, masalan, tarmoq so'rovi yoki faylni o'qish paytida, Python boshqaruvni boshqa thread'ga topshirishi mumkin, bu esa dasturning sezgirligini saqlab qoladi.

Mohiyatan multithreading — sof hisoblash tezligidan ko'ra samaradorlikni oshirishga qaratilgan concurrency texnikasi. U har bir thread qachon ishlashini operatsion tizim boshqarishiga imkon berish orqali ishlaydi, Python esa vazifa kutishi kerak bo'lgan nuqtaga yetganida almashinuvni muvofiqlashtiradi. Bu multithreading'ni I/O bilan tez-tez ishlaydigan dasturlar uchun ideal qiladi, chunki aks holda bo'sh turgan vaqt hamma narsani sekinlashtirgan bo'lardi.

Amalda Python multithreading bir vaqtning o'zida bir nechta faylni yuklab olish, API chaqiruvlari to'plamini qayta ishlash, tarmoq ilovalarida foydalanuvchi so'rovlarini qayta ishlash va asosiy workflow'ni to'xtatmasdan fon vazifalarini boshqarish kabi vazifalarda o'zini yaxshi ko'rsatadi.

Bular parallel CPU bajarilishi kerak bo'lmagan vaziyatlardir. Aksincha, sizga alohida qadamlar tashqi javoblarni kutib turganida ham harakatda davom eta oladigan tizim kerak. Python'ning threading modeli aynan shu narsa uchun mo'ljallangan.

GIL'li Python multithreading GIL'siz tillar bilan qanday taqqoslanishi quyida keltirilgan:

Xususiyat / jihat Python (GIL bilan) GIL'siz tillar (Java, C++, Go va boshqalar)
Global lock Ha, bir vaqtda faqat bitta thread Python bytecode'ini bajaradi. Yo'q, bir nechta thread kodni CPU yadrolari bo'ylab parallel bajarishi mumkin.
Xotira xavfsizligi Interpreter xotirasi (masalan, reference count'lar) GIL tomonidan avtomatik himoyalanadi. Runtime va obyekt xotirasi lock'lar yoki thread-safe API'lar bilan aniq himoyalanishi kerak.
Dasturingizdagi ma'lumotlarni himoyalash Umumiy o'zgaruvchilar uchun baribir aniq lock'lar (Lock, RLock, Event) talab qilinadi. Baribir aniq lock'lar yoki synchronization primitive'lari talab qilinadi.
CPU-bound vazifalar uchun parallelism Cheklangan, thread'lar GIL'ni navbat bilan egallashi kerak. To'liq, thread'lar yadrolar bo'ylab parallel ishlashi mumkin.
I/O-bound vazifalar uchun parallelism Yaxshi – I/O kutishlari paytida GIL bo'shatiladi, bu boshqa thread'larga ishlash imkonini beradi. Yaxshi – I/O-bound concurrency tabiiy ravishda ishlaydi.
Concurrency bug'lari xavfi Interpreter ichki qismlari uchun pastroq; o'z kodingiz uchun o'rtacha. Yuqoriroq – lock nuqtalari ko'proq, race condition/deadlock'lar ehtimoli ham ko'proq.

Eslatma: Hamma Python interpreter'larida ham GIL mavjud emas. Jython, IronPython va ba'zi PyPy tajribalarida u yo'q. PEP 703 va nogil fork kabi tashabbuslar uni CPython tarkibidan butunlay olib tashlashni maqsad qilgan, ayrim kutubxonalar (NumPy, SciPy, Cython, Numba) esa parallelism'ga erishish uchun og'ir hisoblashlar davomida GIL'ni bo'shatadi.

Python'da multithreading'ni amalga oshirish

Endi siz Python thread'lari I/O-bound ishlarda o'zini yaxshi ko'rsatishini bilasiz. Keling, buni klassik misol — multi-threaded fayl yuklab oluvchi dastur bilan amalda qo'llaymiz.

Bir vaqtda bitta sekin tarmoq so'rovini kutish o'rniga, thread'lar bir nechta yuklab olishni concurrent tarzda bajaradi va dasturni sezgir holda saqlaydi.

Ushbu loyihada siz:

  • Bir nechta faylni concurrent tarzda yuklab olasiz

  • Ular tugashi bilan jarayonni kuzatib borasiz

  • Xatolarni xavfsiz qayta ishlaysiz

  • Unumdorlikni o'lchaysiz

Boshlashdan oldin: AI Tutor'dan foydalanishni boshlash uchun ro'yxatdan o'ting. U o'rganishni tezroq va osonroq qiladi. Agar ushbu qo'llanmani o'qish davomida qiyinchilikka duch kelsangiz, AI Tutor'ga savollaringizni to'g'ridan-to'g'ri o'ng tomondagi chatda bering va oddiy tilda tushuntirish oling. Buni doim chaqirsa keladigan ustozingiz bor deb tasavvur qiling. Siz tezroq o'rganasiz, chalkashliklardan qochasiz va ko'nikmalarni sinov va xatolar orqali emas, yo'naltirilgan mashqlar orqali shakllantirasiz.

1-qadam: Ketma-ket ishlaydigan yuklab oluvchi yaratish

Keling, thread'larsiz bir nechta faylni yuklab olishga harakat qilganingizda nima sodir bo'lishidan boshlaylik. Har biri ~2 soniya oladi, demak uchta fayl = jami 6 soniya.

Ushbu dasturda:

  • download_file() 2 soniya kutib turish orqali faylni yuklab olishni simulyatsiya qiladi.

  • main() ichida fayllar ro'yxati bo'ylab tsikl yuramiz va har biri uchun download_file() funksiyasini chaqiramiz.

  • Yuklab olishlar birin-ketin amalga oshgani uchun umumiy ishlash vaqti chiziqli ravishda qo'shilib boradi, CPU esa kutish vaqtida bo'sh turadi.

import time

def download_file(url, filename):
    """Simulate downloading a file"""
    print(f"Starting download: {filename}")
    time.sleep(2)  # Simulate download time
    print(f"✓ Completed: {filename}")
    return filename

def main():
    """Download files one by one (sequential)"""
    files = [
        ("https://example.com/video.mp4", "video.mp4"),
        ("https://example.com/document.pdf", "document.pdf"), 
        ("https://example.com/music.mp3", "music.mp3")
    ]

    print("=== Sequential Downloads ===")
    start_time = time.time()

    for url, filename in files:
        download_file(url, filename)

    total_time = time.time() - start_time
    print(f"Total time: {total_time:.1f} seconds")

if __name__ == "__main__":
    main()

Natija:

=== Sequential Downloads ===
Starting download: video.mp4
✓ Completed: video.mp4
Starting download: document.pdf
✓ Completed: document.pdf
Starting download: music.mp3
✓ Completed: music.mp3
Total time: 6.0 seconds

Buni sinab ko'ring: Ketma-ket yuklab oluvchi kodni ishga tushiring va vaqtini o'lchang. Endi ro'yxatga to'rtinchi faylni qo'shing. Umumiy vaqt qanday o'zgaradi? Ishga tushirishdan oldin vaqtni oldindan aytib bera olasizmi?

2-qadam: Thread'li yuklab oluvchi yaratish

Endi fayllarni bir vaqtda yuklab olish uchun Python standart kutubxonasidagi multithreading module'ini ishga solamiz. Python'da thread yaratishning ikkita asosiy usuli bor.

1-usul: threading.Thread

Bu oddiy, funksional va eng keng tarqalgan yondashuv. U funksiyani thread ichida ishga tushirish kerak bo'lgan bir martalik vazifalar uchun eng mos keladi (barcha thread'larga bajarish uchun target funksiya kerak).

Funksiyadagi thread'larni bajarish uchun bir nechta asosiy thread operatsiyalarini bilishingiz kerak bo'ladi:

Method / operatsiya Tavsif Qachon ishlatiladi Qaytaradi
start() Thread bajarilishini boshlaydi Har bir thread uchun bir marta, yaratilgandan keyin None
join(timeout=None) Thread tugashini kutadi Synchronization kerak bo'lganda None
is_alive() Thread hali ishlayotganini tekshiradi Thread holatini kuzatish uchun Boolean
getName() / name Thread nomini oladi/o'rnatadi Debugging va logging uchun String
ident Thread identifikatorini oladi Noyob identifikatsiya uchun Integer
append() (list) Thread obyektini to'plamga qo'shadi Bir nechta thread'ni boshqarish uchun None

Endi uni amalda ko'raylik:

# basic_threading.py
import threading
import time

def download_file(url, filename):
    """Download a single file"""
    thread_name = threading.current_thread().name
    print(f"[{thread_name}] Starting: {filename}")
    time.sleep(2)  # Simulate download time
    print(f"[{thread_name}] ✓ Completed: {filename}")
    return filename

def threaded_downloads():
    """Download files using multiple threads"""
    files = [
        ("https://example.com/video.mp4", "video.mp4"),
        ("https://example.com/document.pdf", "document.pdf"), 
        ("https://example.com/music.mp3", "music.mp3")
    ]

    print("=== Multi-Threaded Downloads ===")
    start_time = time.time()

    threads = []

    # Step 1: Create threads
    for url, filename in files:
        thread = threading.Thread(
            target=download_file,
            args=(url, filename),
            name=f"Downloader-{filename.split('.')[0]}"
        )
        threads.append(thread)

    # Step 2: Start all threads
    for thread in threads:
        thread.start()

    # Step 3: Wait for all threads to complete
    for thread in threads:
        thread.join()

    total_time = time.time() - start_time
    print(f"Threaded time: {total_time:.1f} seconds")

if __name__ == "__main__":
    threaded_downloads()

Yuqoridagi kod threading.Thread yordamida yuklab olishlarni concurrent tarzda bajarishni namoyish etadi. Har bir faylga o'z thread'i biriktiriladi, u start() bilan ishga tushiriladi va ularni guruh sifatida boshqara olishimiz uchun append(), orqali ro'yxatda saqlanadi. Barcha thread'lar ishga tushirilgach, join() dastur har bir yuklab olish tugaguncha kutishini ta'minlaydi. Ketma-ket bajarish uchun kerak bo'lgan ~6 soniyaga nisbatan uchala fayl ham ~2 soniyada tugaydi, chunki ular birin-ketin emas, concurrent tarzda ishlaydi.

Natija:

=== Multi-Threaded Downloads ===
[Downloader-video] Starting: video.mp4
[Downloader-document] Starting: document.pdf
[Downloader-music] Starting: music.mp3
[Downloader-video] ✓ Completed: video.mp4
[Downloader-document] ✓ Completed: document.pdf
[Downloader-music] ✓ Completed: music.mp3
Threaded time: 2.0 seconds

Buni sinab ko'ring: Thread'li yuklab oluvchini uchta emas, 10 ta faylni yuklab oladigan qilib o'zgartiring. Umumiy vaqt bilan nima sodir bo'ladi? Har bir fayl uchun turli sleep vaqtlarini belgilab ko'ring — umumiy ishlash vaqtini qaysi biri belgilaydi?

2-usul: Thread sinfini subclass qilish

Murakkabroq stsenariylar uchun o'z thread sinfingizni yaratishingiz mumkin. Bu retry'lar, holatni kuzatish yoki natijalarni saqlash kabi maxsus xatti-harakatlarni qo'shish imkonini beradi. Oddiy threading.Thread yondashuvi bilan buni bunchalik toza qilib bo'lmaydi.

Quyida retry logic qo'shadigan DownloadThread sinfi bilan misol keltirilgan:

import threading
import time
import random

class DownloadThread(threading.Thread):
    """Custom thread class for downloading files with built-in retry logic"""

    def __init__(self, url, filename, max_retries=3):
        super().__init__(name=f"Downloader-{filename.split('.')[0]}")
        self.url = url
        self.filename = filename
        self.max_retries = max_retries
        self.result = None
        self.download_time = None
        self.attempts = 0

    def run(self):
        """Called when thread.start() runs"""
        print(f"[{self.name}] Starting download: {self.filename}")
        start_time = time.time()

        for attempt in range(1, self.max_retries + 1):
            self.attempts = attempt
            try:
                print(f"[{self.name}] Attempt {attempt} for {self.filename}")
                time.sleep(2)  # Simulate download

                if attempt == 1 and random.random() < 0.2:  # 20% chance of failure
                    raise Exception("Network timeout")

                self.download_time = time.time() - start_time
                self.result = "success"
                print(f"[{self.name}] {self.filename} downloaded in {self.download_time:.1f}s")
                return
            except Exception as e:
                print(f"[{self.name}] Attempt {attempt} failed: {e}")
                if attempt < self.max_retries:
                    time.sleep(0.5)  # brief pause before retry
                else:
                    self.result = "failed"
                    self.download_time = time.time() - start_time
                    print(f"[{self.name}] Permanently failed after {attempt} attempts")

def demonstrate_custom_threads():
    files = [
        ("https://example.com/video.mp4", "video.mp4"),
        ("https://example.com/document.pdf", "document.pdf"),
        ("https://example.com/music.mp3", "music.mp3")
    ]

    print("=== Custom Thread Class Downloads ===")
    download_threads = [DownloadThread(url, filename) for url, filename in files]

    start_time = time.time()
    for t in download_threads: t.start()
    for t in download_threads: t.join()
    total_time = time.time() - start_time

    print("\n=== Download Results ===")
    successful = 0
    for t in download_threads:
        status = "Success" if t.result == "success" else "Failed"
        print(f"{status} - {t.filename}: {t.result} ({t.attempts} attempts, {t.download_time:.1f}s)")
        if t.result == "success": successful += 1

    print(f"Total time: {total_time:.1f} seconds")
    print(f"Success rate: {successful}/{len(download_threads)}")

if __name__ == "__main__":
    demonstrate_custom_threads()

Yuqoridagi kod:

  • threading.Thread'dan meros oladigan DownloadThread sinfini e'lon qiladi va thread to'g'ri sozlanishi uchun super().init()'ni chaqiradi

  • run() ichiga retry logic qo'shadi va retry'lar qanday ishlashini ko'rsatish uchun nosozliklarni simulyatsiya qiladi

  • Natijalarni (muvaffaqiyat yoki muvaffaqiyatsizlik), urinishlar sonini va umumiy yuklab olish vaqtini kuzatib boradi

  • Bir nechta maxsus thread yaratadi, ularni ishga tushiradi va join() bilan tugashini kutadi

  • Natijalar xulosasini chiqaradi: qaysi fayllar muvaffaqiyatli yuklangani va qancha vaqt olganini ko'rsatadi

Natija:

=== Custom Thread Class Downloads ===
[Downloader-video] Starting download: video.mp4
[Downloader-document] Starting download: document.pdf
[Downloader-music] Starting download: music.mp3
[Downloader-video] Attempt 1 for video.mp4
[Downloader-document] Attempt 1 for document.pdf
[Downloader-music] Attempt 1 for music.mp3
[Downloader-document] document.pdf downloaded in 2.0s
[Downloader-music] music.mp3 downloaded in 2.0s
[Downloader-video] Attempt 1 failed: Network timeout
[Downloader-video] Attempt 2 for video.mp4
[Downloader-video] video.mp4 downloaded in 2.5s

=== Download Results ===
Success - video.mp4: success (2 attempts, 2.5s)
Success - document.pdf: success (1 attempts, 2.0s)
Success - music.mp3: success (1 attempts, 2.0s)
Total time: 2.5 seconds
Success rate: 3/3

Buni sinab ko'ring: Necha marta retry qilganini kuzatib boradigan maxsus thread sinfini yarating. Barcha thread'lar bo'yicha jami retry'larni sanash uchun class variable qo'shing. Bu hisob lock'larsiz aniq bo'ladimi?

3-qadam: Thread'larga argument'lar uzatish

Real hayotdagi thread funksiyalariga bittadan ko'proq parametr kerak bo'ladi. Python thread'lari ham positional (args), ham keyword (kwargs) argument'larni qabul qiladi.

Ma'lumot uzatishda asosiy savol shunday: thread'lar bu ma'lumotni birgalikda ishlatishi xavfsizmi yoki xavflimi?

Xavfsiz ma'lumotlarga string'lar, sonlar va tuple'lar kabi immutable type'lar kiradi. Thread'lar ularni erkin ulasha oladi, chunki ularni joyida o'zgartirib bo'lmaydi, shu sababli hech qanday buzilish yuz bermaydi. Keling, misolni ko'rib chiqaylik:

import threading, time, random

def download_with_options(file_id, filename, size_mb=1, priority="normal", max_retries=3):
    """Simulate downloading a file with various options and retry logic"""
    thread = threading.current_thread().name
    print(f"[{thread}] Download {file_id}: {filename} ({size_mb}MB, {priority})")

    # Calculate download time based on size and priority
    base_time = size_mb * 0.3
    if priority == "high": 
        base_time *= 0.7  # High priority downloads faster
    if priority == "low": 
        base_time *= 1.3  # Low priority downloads slower

    # Retry logic with simulated failures
    for attempt in range(max_retries):
        try:
            print(f"[{thread}] Attempt {attempt+1} for {filename}")
            time.sleep(base_time)  # Simulate download time

            # Simulate random network failures on first attempt
            if attempt == 0 and random.random() < 0.2:
                raise Exception("Network timeout")

            print(f"[{thread}] Downloaded {filename} successfully")
            return
        except Exception as e:
            print(f"[{thread}] Failed attempt {attempt+1}: {e}")
            time.sleep(0.5)  # Wait before retry

def demonstrate_safe():
    """Show how immutable arguments are safely passed to threads"""
    print("=== Safe argument passing ===")

    # Create threads with different argument combinations
    t1 = threading.Thread(target=download_with_options, args=(1, "video.mp4"))
    t2 = threading.Thread(
        target=download_with_options,
        args=(2, "document.pdf"),
        kwargs={"size_mb": 5, "priority": "high"}
    )
    t3 = threading.Thread(
        target=download_with_options,
        args=(3, "music.mp3"),
        kwargs={"priority": "low"}
    )

    # Start all threads
    for t in [t1, t2, t3]: 
        t.start()

    # Wait for all threads to complete
    for t in [t1, t2, t3]: 
        t.join()

if __name__ == "__main__":
    demonstrate_safe()

Bu immutable qiymatlar bilan xavfsiz threading'ni ko'rsatadi. Har bir thread bu qiymatlarning o'z nusxasini oladi, shuning uchun ular bir-biriga xalaqit bera olmaydi.

Natija:

=== Safe argument passing ===
[Thread-1] Download 1: video.mp4 (1MB, normal)
[Thread-2] Download 2: document.pdf (5MB, high)
[Thread-3] Download 3: music.mp3 (1MB, low)
[Thread-1] Attempt 1 for video.mp4
[Thread-2] Attempt 1 for document.pdf
[Thread-3] Attempt 1 for music.mp3
[Thread-1] Downloaded video.mp4 successfully
...

Ikkinchi misolda xavfli argument va ma'lumotlar ko'rsatilgan. List'lar va dictionary'lar kabi mutable obyektlarni ulashish xavfli. Agar bir nechta thread ularni bir vaqtda yangilasa, o'zgarishlar to'qnashib, natijalarni buzishi mumkin. Mana yana bir misol:

import threading, time, random

# Global shared data structures - dangerous to modify from multiple threads
stats = {"total": 0, "completed": 0, "failed": 0}
results = []

def download_with_shared_stats(filename, stats, results):
    """Demonstrate unsafe modification of shared mutable data"""
    thread = threading.current_thread().name

    # UNSAFE: Multiple threads can modify stats at the same time
    stats["total"] += 1
    print(f"[{thread}] Starting {filename}")

    try:
        # Simulate download with random duration and failure
        time.sleep(random.uniform(1, 2))
        if random.random() < 0.3:
            raise Exception("Network error")

        # UNSAFE: Race condition when updating shared data
        stats["completed"] += 1
        results.append(f"{filename} success")
        print(f"[{thread}] Completed {filename}")

    except Exception as e:
        # UNSAFE: Another race condition
        stats["failed"] += 1
        results.append(f"{filename} failed: {e}")
        print(f"[{thread}] Failed {filename}")

def demonstrate_dangerous():
    """Show how shared mutable data leads to race conditions"""
    print("=== Dangerous: sharing mutable data ===")

    files = ["file1.zip", "file2.pdf", "file3.mp4"]

    # Create threads that all modify the same shared data
    threads = [
        threading.Thread(target=download_with_shared_stats, args=(f, stats, results)) 
        for f in files
    ]

    # Start and wait for all threads
    for t in threads: 
        t.start()
    for t in threads: 
        t.join()

    print("\n=== Results (may be corrupted) ===")
    print("Stats:", stats)
    print("Results list length:", len(results))

if __name__ == "__main__":
    demonstrate_dangerous()

Bu kod mutable ma'lumotlar bilan bog'liq muammoni namoyish etadi. Bir nechta thread bitta dictionary yoki list'ni o'zgartirganda, ularning yangilanishlari bir-birining ustidan yozib yuborishi mumkin, bu esa noto'g'ri yakuniy natijalarga olib keladi.

Natija (izchil emas):

=== Dangerous: sharing mutable data ===
[Thread-1] Starting file1.zip
[Thread-2] Starting file2.pdf
[Thread-3] Starting file3.mp4
[Thread-1] Completed file1.zip
[Thread-2] Failed file2.pdf
[Thread-3] Completed file3.mp4

=== Results (may be corrupted) ===
Stats: {'total': 2, 'completed': 2, 'failed': 1}   # wrong total
Results list length: 3

Buni sinab ko'ring: Bir nechta thread'ga dictionary uzating, bunda har bir thread boshqa key'ni o'zgartirsin. Uni 10 marta ishga tushiring. Har doim bir xil natija olasizmi? Endi thread'lar bir xil key'ni o'zgartiradigan qilib ko'ring — nima o'zgaradi?

4-qadam: Race condition'larni bartaraf etish

3-qadamdagi umumiy ma'lumotlar dastlab joyidadek tuyuldi, lekin biz bir nechta thread bir-birining ustidan yozib yuborishi mumkinligini ko'rdik. Xuddi shu muammo closure'lardagi umumiy holat bilan ham uchraydi, bunda nonlocal ulashishni aniq ko'rsatib qo'yadi.

Race condition bir nechta thread umumiy ma'lumotni bir vaqtda o'zgartirishga uringanda yuzaga keladi, buni 3-qadamning 2-misolida ko'rdik. Yakuniy natija "poyga"da qaysi thread yutishiga bog'liq bo'ladi, shu sababli natijalar izchil bo'lmaydi.

import threading
import time

# Global download statistics shared by all threads
download_counter = 0
bytes_downloaded = 0

def download_with_tracking(filename, size_mb):
    """Download file and track statistics - UNSAFE"""
    global download_counter, bytes_downloaded

    print(f"Starting download: {filename}")

    # Simulate downloading in small chunks
    chunks = size_mb * 100  # 100 chunks per MB
    for _ in range(chunks):
        # These lines are NOT atomic - each is actually multiple operations:
        # 1. Read current value of counter
        # 2. Add 1 to that value  
        # 3. Store the result back to counter
        # Other threads can interfere between these steps
        download_counter += 1
        bytes_downloaded += 10240  # 10KB per chunk

        # Small delay to make race condition more likely
        time.sleep(0.0001)

    print(f"Completed: {filename}")

def demonstrate_download_race():
    """Show how race conditions corrupt download statistics"""
    global download_counter, bytes_downloaded
    download_counter = 0
    bytes_downloaded = 0

    print("=== Download Race Condition Demo ===")

    files = [
        ("video.mp4", 20),
        ("document.pdf", 5),
        ("music.mp3", 10)
    ]

    # Calculate expected totals
    expected_chunks = sum(size * 100 for _, size in files)
    expected_bytes = expected_chunks * 10240

    threads = []
    start_time = time.time()

    # Create threads for each download
    for filename, size_mb in files:
        thread = threading.Thread(
            target=download_with_tracking, 
            args=(filename, size_mb),
            name=f"Downloader-{filename.split('.')[0]}"
        )
        threads.append(thread)

    # Start all downloads simultaneously
    for thread in threads:
        thread.start()

    # Wait for all to complete
    for thread in threads:
        thread.join()

    total_time = time.time() - start_time

    print(f"\n=== Results (likely corrupted due to race conditions) ===")
    print(f"Expected chunks: {expected_chunks:,}")
    print(f"Actual chunks: {download_counter:,}")
    print(f"Lost chunks: {expected_chunks - download_counter:,}")
    print(f"Expected bytes: {expected_bytes:,}")
    print(f"Actual bytes: {bytes_downloaded:,}")
    print(f"Lost bytes: {expected_bytes - bytes_downloaded:,}")
    print(f"Accuracy: {(download_counter/expected_chunks)*100:.1f}%")
    print(f"Time taken: {total_time:.2f} seconds")

if __name__ == "__main__":
    demonstrate_download_race()

Yuqoridagi kod yuklab olishlarni kuzatishda race condition'lar nima uchun yuzaga kelishini ko'rsatadi. download_counter += 1 operatsiyasi oddiy ko'rinadi, lekin aslida u uchta alohida qadamdan iborat: joriy qiymatni o'qish, unga bir qo'shish va natijani qayta saqlash. Bir nechta thread buni bir vaqtda bajarganda, ular bir xil boshlang'ich qiymatni o'qib, ikkalasi ham unga bir qo'shishi mumkin, natijada increment'lardan biri amalda yo'qoladi.

Natija:

=== Download Race Condition Demo ===
Starting download: video.mp4
Starting download: document.pdf
Starting download: music.mp3
Completed: document.pdf
Completed: music.mp3
Completed: video.mp4

=== Results (likely corrupted due to race conditions) ===
Expected chunks: 3,500
Actual chunks: 3,247
Lost chunks: 253
Expected bytes: 35,840,000
Actual bytes: 33,248,320
Lost bytes: 2,591,680
Accuracy: 92.8%
Time taken: 0.89 seconds

Buni sinab ko'ring: Beshta thread'ning har biri hisoblagichni 50 000 marta oshiradigan dastur yozing. Uni bir necha marta ishga tushiring va yakuniy qiymatlarni yozib boring.

5-qadam: Race condition'larni lock'lar bilan tuzatish

Lock obyekti bir vaqtda faqat bitta thread kodning critical section'iga kira olishini ta'minlaydi va shu orqali race condition'larning oldini oladi. Buni nazorat punkti deb tasavvur qiling: thread'lar navbatga turadi va faqat bittasi o'tadi, u ishini tugatmaguncha boshqalar kutib turadi. Keling, context manager'dan foydalanilgan misolni ko'rib chiqaylik:

import threading
import time

# Global download statistics and lock to protect them
download_counter = 0
bytes_downloaded = 0
completed_files = 0
stats_lock = threading.Lock()

def safe_download_with_tracking(filename, size_mb):
    """Safely download file and track statistics using a lock"""
    global download_counter, bytes_downloaded, completed_files

    print(f"Starting download: {filename}")

    # Simulate downloading in small chunks
    chunks = size_mb * 100  # 100 chunks per MB
    for _ in range(chunks):
        # The 'with' statement acts as a context manager for the lock
        with stats_lock:
            # Only one thread can execute these lines at a time
            # This prevents race conditions
            download_counter += 1
            bytes_downloaded += 10240  # 10KB per chunk

        # Small delay to simulate actual download time
        time.sleep(0.0001)

    # Safely update completion count
    with stats_lock:
        completed_files += 1

    print(f"Completed: {filename}")

def demonstrate_safe_downloads():
    """Show how locks prevent race conditions in download tracking"""
    global download_counter, bytes_downloaded, completed_files
    download_counter = 0
    bytes_downloaded = 0
    completed_files = 0

    print("=== Safe Download Statistics with Lock ===")

    files = [
        ("video.mp4", 20),
        ("document.pdf", 5),
        ("music.mp3", 10),
        ("image.jpg", 3)
    ]

    # Calculate expected totals
    expected_chunks = sum(size * 100 for _, size in files)
    expected_bytes = expected_chunks * 10240

    threads = []
    start_time = time.time()

    # Create threads for each download
    for filename, size_mb in files:
        thread = threading.Thread(
            target=safe_download_with_tracking, 
            args=(filename, size_mb),
            name=f"Downloader-{filename.split('.')[0]}"
        )
        threads.append(thread)

    # Start all downloads simultaneously
    for thread in threads:
        thread.start()

    # Wait for all to complete
    for thread in threads:
        thread.join()

    total_time = time.time() - start_time

    print(f"\n=== Results (now accurate with locks) ===")
    print(f"Expected chunks: {expected_chunks:,}")
    print(f"Actual chunks: {download_counter:,}")
    print(f"Expected bytes: {expected_bytes:,}")
    print(f"Actual bytes: {bytes_downloaded:,}")
    print(f"Completed files: {completed_files}/{len(files)}")
    print(f"Accuracy: 100.0%")
    print(f"Time taken: {total_time:.2f} seconds")
    print("✓ All statistics are now accurate!")

if __name__ == "__main__":
    demonstrate_safe_downloads()

Lock obyektisiz increment'lar to'qnashib ketadi va natija kamroq chiqadi. U bilan esa yangilanishlar sinxron bajariladi va lock context manager tomonidan to'g'ri bo'shatiladi.

threading.Thread() bilan yaratilgan har bir thread instance'i lock'lar bilan himoyalangan umumiy ma'lumotlarga xavfsiz murojaat qila oladi.

Natija:

=== Safe Download Statistics with Lock ===
Starting download: video.mp4
Starting download: document.pdf
Starting download: music.mp3
Starting download: image.jpg
Completed: image.jpg
Completed: document.pdf
Completed: music.mp3
Completed: video.mp4

=== Results (now accurate with locks) ===
Expected chunks: 3,800
Actual chunks: 3,800
Expected bytes: 38,912,000
Actual bytes: 38,912,000
Completed files: 4/4
Accuracy: 100.0%
Time taken: 1.02 seconds
✓ All statistics are now accurate!

Lock'larning ikki turi mavjud. Yuqoridagisi 'Lock' deb ataladi, ikkinchisi esa 'RLock'.

  • Lock: Agar o'sha thread uni ikki marta acquire qilishga urinsa, deadlock yuz beradi.

  • RLock (Reentrant Lock): O'sha thread'ga uni bir necha marta xavfsiz acquire qilish imkonini beradi.

Ushbu namunaviy dastur farqni ko'rsatadi:

# download_manager_locks.py
import threading
import time

# Regular lock and reentrant lock for comparison
connection_lock = threading.Lock()
reentrant_connection_lock = threading.RLock()

active_downloads = 0

class DownloadManager:
    """Example showing practical use of RLock for download management"""
    def __init__(self):
        self.active_downloads = 0
        self.completed_downloads = 0
        self.total_bytes = 0
        self.lock = threading.RLock()  # Use RLock for methods that call each other

    def start_download(self, filename, size_mb):
        """Begin a download"""
        with self.lock:
            self.active_downloads += 1
            print(f"Starting download: {filename} ({size_mb}MB)")
            self._log_activity("started", filename, size_mb)
            time.sleep(0.1)  # Simulate initial setup

    def complete_download(self, filename, size_mb):
        """Complete a download"""
        with self.lock:
            if self.active_downloads > 0:
                self.active_downloads -= 1
                self.completed_downloads += 1
                self.total_bytes += size_mb * 1024 * 1024
                print(f"Completed download: {filename}")
                self._log_activity("completed", filename, size_mb)
                return True
            return False

    def _log_activity(self, action, filename, size_mb):
        """Log download activity - also needs the lock"""
        with self.lock:  # Same thread can acquire RLock again
            print(f"  LOG: {action} {filename} ({size_mb}MB) - Active: {self.active_downloads}")

    def transfer_and_cleanup(self, filename, size_mb):
        """Transfer file and cleanup - calls multiple methods needing the lock"""
        with self.lock:
            if self.complete_download(filename, size_mb):  # This also needs the lock
                print(f"  Cleaned up {filename}")

def demonstrate_download_manager():
    """Show RLock in a practical download scenario"""
    print("=== Download Manager with RLock ===")

    manager = DownloadManager()

    # These operations work because RLock allows the same thread
    # to acquire the lock multiple times
    manager.start_download("video.mp4", 25)
    manager.start_download("music.mp3", 8)
    manager.complete_download("video.mp4", 25)
    manager.transfer_and_cleanup("music.mp3", 8)

    print(f"Final stats: {manager.completed_downloads} completed, {manager.total_bytes//1024//1024}MB total")

if __name__ == "__main__":
    demonstrate_download_manager()

Natija:

=== Download Manager with RLock ===
Starting download: video.mp4 (25MB)
  LOG: started video.mp4 (25MB) - Active: 1
Starting download: music.mp3 (8MB)
  LOG: started music.mp3 (8MB) - Active: 2
Completed download: video.mp4
  LOG: completed video.mp4 (25MB) - Active: 1
Completed download: music.mp3
  LOG: completed music.mp3 (8MB) - Active: 0
  Cleaned up music.mp3
Final stats: 2 completed, 33MB total

RLock o'sha thread uni necha marta acquire qilganini ichki hisoblagich yordamida kuzatib boradi va barcha acquire'lar mos ravishda bo'shatilgandagina lock'ni qo'yib yuboradi. Bu object-oriented kodda juda muhim, chunki u yerda method'lar ko'pincha xuddi shu lock obyektiga muhtoj bo'lgan boshqa method'larni chaqiradi.

Masalan, DownloadManager sinfi transfer_and_cleanup() method'i complete_download()'ni chaqirganda RLock'dan xavfsiz foydalana oladi, chunki ikkalasiga ham lock obyekti kerak. RLock bo'lmasa, bu deadlock'ga olib kelardi.

Buni sinab ko'ring: Lock obyekti'ni acquire qiladigan, so'ngra o'zini o'zi chaqiradigan rekursiv funksiya yarating. Uni Lock bilan (deadlock bo'ladi) va RLock bilan (ishlaydi) sinab ko'ring. Python'ning rekursiya chegarasiga yetguncha RLock bilan nechta rekursiv chaqiruv qila olasiz?

6-qadam: Deadlock'larni ehtiyotkorlik bilan bartaraf etish

Deadlock'lar ishlayotgan thread'lar cheksiz muddatga bloklanib qolganda yuz beradi. Bu bir nechta thread'ning har biri bitta lock obyektini ushlab turib, boshqasining lock obyektini kutganda sodir bo'ladi. Hech biri davom eta olmagani uchun Python dasturi qotib qoladi.

Quyida yuklab olish bandwidth'i va ulanishlarni boshqarish bilan bog'liq misol keltirilgan:

# download_deadlock.py
import threading
import time

# Two locks that will cause problems if acquired in different orders
bandwidth_lock = threading.Lock()
connection_lock = threading.Lock()

def download_task1(filename):
    """Download using bandwidth lock first, then connection lock"""
    print(f"Task1: Starting download of {filename}")
    print("Task1: Acquiring bandwidth lock...")
    with bandwidth_lock:
        print("Task1: Got bandwidth lock, checking available bandwidth...")
        time.sleep(0.1)  # Give task2 time to acquire connection_lock

        print("Task1: Now acquiring connection lock...")
        with connection_lock:  # This will wait forever if task2 holds connection_lock
            print(f"Task1: Successfully downloaded {filename}")

def download_task2(filename):
    """Download using connection lock first, then bandwidth lock - DANGEROUS"""
    print(f"Task2: Starting download of {filename}")
    print("Task2: Acquiring connection lock...")
    with connection_lock:
        print("Task2: Got connection lock, establishing connection...")
        time.sleep(0.1)  # Give task1 time to acquire bandwidth_lock

        print("Task2: Now acquiring bandwidth lock...")
        with bandwidth_lock:  # This will wait forever if task1 holds bandwidth_lock
            print(f"Task2: Successfully downloaded {filename}")

def demonstrate_download_deadlock():
    """Demonstrate deadlock in download resource management"""
    print("=== Download Deadlock Demo ===")

    # Create and start threads
    t1 = threading.Thread(target=download_task1, args=("video.mp4",), name="Downloader-1")
    t2 = threading.Thread(target=download_task2, args=("music.mp3",), name="Downloader-2")

    print("Starting download threads - this may deadlock...")
    t1.start()
    t2.start()

    # These joins may wait forever if deadlock occurs
    try:
        t1.join(timeout=5)  # Wait max 5 seconds
        t2.join(timeout=5)

        if t1.is_alive() or t2.is_alive():
            print("DEADLOCK DETECTED! Threads are still running after timeout.")
            print("In a real application, you'd need to handle this situation.")
        else:
            print("Both downloads completed successfully")
    except KeyboardInterrupt:
        print("Download interrupted by user")

if __name__ == "__main__":

Bu kod lock'larni turli tartibda acquire qilish yuklab olishlarni boshqarishda qanday qilib deadlock'ni keltirib chiqarishi mumkinligini ko'rsatadi. 1-vazifa bandwidth_lock'ni oladi va connection_lock'ni kutadi, 2-vazifa esa connection_lock'ni oladi va bandwidth_lock'ni kutadi. Hech biri davom eta olmaydi, shuning uchun Python dasturi qotib qoladi. Yechim — barcha ishlayotgan thread'larda bir nechta lock'ni har doim bir xil tartibda acquire qilish.

Buni sinab ko'ring: Uchta yuklab olish thread'i va uchta resurs lock'i (bandwidth, connection, storage) bilan "dining philosophers" muammosini yarating. Deadlock'ni yuzaga keltira olasizmi? Endi timeout'ga asoslangan yechimni amalga oshiring. U deadlock'ning oldini oladimi?

7-qadam: Daemon thread'lar yaratish

Oddiy thread'lar Python dasturdan chiqishidan oldin tugashi shart. Daemon thread'lar esa fonda ishlaydi va main thread tugashi bilan avtomatik to'xtaydi; ular dasturning yakunlanishini bloklamasdan tashqi hodisalarni qayta ishlaydi. Asosiy dastur chiqib ketganda ular yakunlangan thread'larga aylanadi.

import threading
import time

def download_monitor():
    """A daemon task that monitors download progress indefinitely"""
    while True:
        print("Monitoring download queue...")
        time.sleep(2)

def download_heartbeat():
    """A daemon task that sends keep-alive signals"""
    while True:
        print("Download service heartbeat - keeping connections alive")
        time.sleep(3)

def actual_download(filename, size_mb):
    """Simulate an actual download task"""
    print(f"Downloading {filename} ({size_mb}MB)...")
    download_time = size_mb * 0.3
    time.sleep(download_time)
    print(f"{filename} download complete!")

def demonstrate_daemon_downloads():
    """Show how daemon threads work with download monitoring"""
    print("=== Daemon Thread Download Demo ===")

    # Create daemon threads for background monitoring
    monitor_thread = threading.Thread(target=download_monitor, daemon=True, name="Monitor")
    heartbeat_thread = threading.Thread(target=download_heartbeat, daemon=True, name="Heartbeat")

    # Start daemon threads
    monitor_thread.start()
    heartbeat_thread.start()

    # Do some actual downloads (non-daemon threads)
    download_threads = []
    files = [
        ("video.mp4", 4),
        ("document.pdf", 2),
        ("music.mp3", 3)
    ]

    for filename, size_mb in files:
        t = threading.Thread(
            target=actual_download, 
            args=(filename, size_mb),
            name=f"Download-{filename}"
        )
        download_threads.append(t)
        t.start()

    # Wait for actual downloads to complete
    for t in download_threads:
        t.join()

    print("All downloads completed!")
    print("Main program ending - daemon threads will stop automatically")

    # Program exits here - daemon threads stop automatically
    # If daemon=False, program would wait for monitor and heartbeat to finish

if __name__ == "__main__":
    demonstrate_daemon_downloads()

Natija:

=== Daemon Thread Download Demo ===
Monitoring download queue...
Download service heartbeat - keeping connections alive
Downloading video.mp4 (4MB)...
Downloading document.pdf (2MB)...
Downloading music.mp3 (3MB)...
document.pdf download complete!
Monitoring download queue...
Download service heartbeat - keeping connections alive
music.mp3 download complete!
video.mp4 download complete!
All downloads completed!
Main program ending - daemon threads will stop automatically

Daemon thread'lar fonda uzluksiz ishlaydigan o'ziga xos vazifani (monitoring va heartbeat) bajaradi, lekin main thread tugagach, Python daemon thread'larni avtomatik to'xtatadi va dasturdan chiqadi. Bu oddiy thread'lardan farq qiladi: ularda Python chiqishdan oldin barcha ishlayotgan thread'lar, jumladan har qanday yakunlangan thread'lar ham tugashini kutadi.

Buni sinab ko'ring: Har soniyada yuklab olish statistikasini faylga yozib boradigan daemon thread yarating. Besh soniyadan keyin main thread'ni to'xtating. Faylni tekshiring: beshta yozuvning hammasi bajarildimi? Nega ba'zilari yetishmasligi mumkin?

8-qadam: Thread-safe queue'lar (producer-consumer)

Queue'lar thread'larga ishni xavfsiz taqsimlash imkonini beradi. Ular locking'ni ichkarida o'zi boshqaradi, shuning uchun lock obyektini qo'lda boshqarishga hojat qolmaydi. Bu pattern, ayniqsa, boshqa thread'dan kelgan yuklab olish vazifalarini qayta ishlaydigan consumer'ingiz bo'lganda foydali. Buning uchun queue module'idan foydalanamiz. Umumiy dictionary ham thread'lar o'rtasida boshqa har qanday mutable tuzilma kabi ehtiyotkorlikni talab qiladi.

# download_queue.py
import threading
import queue
import time
import random

# Thread-safe queue for download jobs
download_queue = queue.Queue()

def download_producer():
    """Generate download jobs and put them in the queue"""
    download_jobs = [
        ("video1.mp4", 20),
        ("video2.mp4", 15),
        ("document1.pdf", 3),
        ("document2.pdf", 5),
        ("music1.mp3", 8),
        ("music2.mp3", 12)
    ]

    print("Producer: Adding download jobs to queue...")
    for i, (filename, size_mb) in enumerate(download_jobs, 1):
        job = {'id': i, 'filename': filename, 'size_mb': size_mb}
        download_queue.put(job)  # Thread-safe operation
        print(f"   Added job {i}: {filename} ({size_mb}MB)")
        time.sleep(0.3)  # Simulate time between job creation

    # Signal end of work with sentinel values for consumers
    for _ in range(2):  # We'll have 2 consumer threads
        download_queue.put(None)
    print("Producer: All jobs queued, sent stop signals")

def download_consumer(consumer_id):
    """Take download jobs from the queue and process them"""
    downloaded_files = []

    print(f"Consumer-{consumer_id}: Starting download worker")

    while True:
        job = download_queue.get()  # Thread-safe operation, blocks if queue is empty

        if job is None:
            # Sentinel value means no more work
            print(f"Consumer-{consumer_id}: Received stop signal, shutting down")
            break

        # Process the download job
        filename = job['filename']
        size_mb = job['size_mb']

        print(f"Consumer-{consumer_id}: Starting download {filename}")

        # Simulate download time
        download_time = size_mb * 0.15 + random.uniform(0, 0.5)
        time.sleep(download_time)

        downloaded_files.append(filename)
        print(f"Consumer-{consumer_id}: Completed {filename} in {download_time:.1f}s")

        download_queue.task_done()  # Mark job as completed

    print(f"Consumer-{consumer_id}: Downloaded {len(downloaded_files)} files")

def demonstrate_download_queue():
    """Show producer-consumer pattern for download management"""
    print("=== Producer-Consumer Download Queue ===")

    # Create producer thread
    producer = threading.Thread(target=download_producer, name="Producer")

    # Create multiple consumer threads
    consumers = []
    for i in range(1, 3):  # 2 consumer workers
        consumer = threading.Thread(
            target=download_consumer, 
            args=(i,),
            name=f"Consumer-{i}"
        )
        consumers.append(consumer)

    start_time = time.time()

    # Start all threads
    producer.start()
    for consumer in consumers:
        consumer.start()

    # Wait for all to complete
    producer.join()
    for consumer in consumers:
        consumer.join()

    total_time = time.time() - start_time
    print(f"\nAll downloads completed in {total_time:.1f} seconds")

if __name__ == "__main__":
    demonstrate_download_queue()

Natija:

=== Producer-Consumer Download Queue ===
Producer: Adding download jobs to queue...
Consumer-1: Starting download worker
Consumer-2: Starting download worker
   Added job 1: video1.mp4 (20MB)
Consumer-1: Starting download video1.mp4
   Added job 2: video2.mp4 (15MB)
Consumer-2: Starting download video2.mp4
   Added job 3: document1.pdf (3MB)
Consumer-2: Completed video2.mp4 in 2.7s
Consumer-2: Starting download document1.pdf
   Added job 4: document2.pdf (5MB)
Consumer-2: Completed document1.pdf in 0.8s
Consumer-2: Starting download document2.pdf
   Added job 5: music1.mp3 (8MB)
Consumer-1: Completed video1.mp4 in 3.2s
Consumer-1: Starting download music1.mp3
   Added job 6: music2.mp3 (12MB)
Consumer-2: Completed document2.pdf in 1.1s
Consumer-2: Starting download music2.mp3
Producer: All jobs queued, sent stop signals
Consumer-1: Completed music1.mp3 in 1.4s
Consumer-1: Received stop signal, shutting down
Consumer-2: Completed music2.mp3 in 2.0s
Consumer-2: Received stop signal, shutting down
Consumer-1: Downloaded 2 files
Consumer-2: Downloaded 4 files

queue module'i thread'lar o'rtasida xavfsiz aloqa kanalini taqdim etadi. Producer chaqiruvchi thread'dan yuklab olish vazifalarini queue'ga joylaydi, consumer'lar esa ularni alohida thread'da qayta ishlash uchun oladi.

queue module'i barcha locking'ni ichkarida boshqargani uchun race condition yoki ma'lumotlar buzilishi xavfi yo'q. Chaqiruvchining thread'i shu mexanizm orqali boshqa thread'lar bilan xavfsiz aloqa qila oladi.

Buni sinab ko'ring: Producer yuklab olish vazifalarini consumer'lar qayta ishlay olishidan tezroq yaratadigan producer-consumer tizimini quring. Queue'ga maksimal hajm qo'shing. Queue to'lib qolganda nima sodir bo'ladi?

9-qadam: concurrent.futures bilan thread pool'lar yaratish

Bir nechta thread'ni qo'lda yaratish va boshqarish chalkash jarayon bo'lishi mumkin. ThreadPoolExecutor buni siz uchun bajaradi: u yuklab olish vazifalari uchun worker thread'lar pool'idan qayta foydalanadi. Siz bir nechta vazifani yuborasiz, pool esa ichkarida Python'ning threading module'idan foydalanib, ularni bo'sh worker'lar o'rtasida taqsimlaydi.

Quyida batch tarzda yuklab olish uchun thread pool'lar misoli keltirilgan:

# download_thread_pool.py
import time
import concurrent.futures
import random

def download_file_with_retry(file_info):
    """Simulate downloading a file with retry logic"""
    filename, size_mb = file_info

    print(f"Starting download: {filename} ({size_mb}MB)")

    # Simulate variable download time
    base_time = size_mb * 0.15
    download_time = base_time + random.uniform(0, base_time * 0.3)

    # Simulate occasional network issues
    for attempt in range(3):
        try:
            time.sleep(download_time / 3)  # Simulate partial download

            if attempt == 0 and random.random() < 0.15:
                raise Exception("Network timeout")

            print(f"Completed: {filename} in {download_time:.1f}s")
            return {
                'filename': filename,
                'status': 'success',
                'size_mb': size_mb,
                'download_time': download_time,
                'attempts': attempt + 1
            }

        except Exception as e:
            if attempt < 2:
                print(f"Retry {attempt + 1} for {filename}: {e}")
                time.sleep(0.2)
            else:
                print(f"Failed: {filename} after 3 attempts")
                return {
                    'filename': filename,
                    'status': 'failed',
                    'error': str(e),
                    'attempts': 3
                }

def demonstrate_download_pool():
    """Show how thread pools simplify concurrent downloads"""
    download_files = [
        ("video1.mp4", 25), ("video2.mp4", 30), ("video3.mp4", 20),
        ("doc1.pdf", 5), ("doc2.pdf", 3), ("doc3.pdf", 8),
        ("music1.mp3", 12), ("music2.mp3", 15), ("music3.mp3", 10)
    ]

    print("=== Thread Pool Download Manager ===")
    print(f"Downloading {len(download_files)} files using thread pool...")

    start_time = time.time()

    # Create a pool of 3 worker threads
    with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
        # Submit all download tasks to the pool
        future_to_file = {
            executor.submit(download_file_with_retry, file_info): file_info
            for file_info in download_files
        }

        # Collect results as they complete
        results = []
        for future in concurrent.futures.as_completed(future_to_file):
            try:
                result = future.result()
                results.append(result)
            except Exception as exc:
                print(f'Download generated exception: {exc}')

    total_time = time.time() - start_time

    # Analyze results
    successful = [r for r in results if r['status'] == 'success']
    failed = [r for r in results if r['status'] == 'failed']
    total_size = sum(r['size_mb'] for r in successful)

    print(f"\n=== Download Pool Results ===")
    print(f"Total time: {total_time:.1f} seconds")
    print(f"Successful downloads: {len(successful)}/{len(download_files)}")
    print(f"Failed downloads: {len(failed)}")
    print(f"Total data downloaded: {total_size}MB")
    if total_size > 0:
        print(f"Average download speed: {total_size/total_time:.1f} MB/s")

if __name__ == "__main__":
    demonstrate_download_pool()

Natija:

=== Thread Pool Download Manager ===
Downloading 9 files using thread pool...
Starting download: video1.mp4 (25MB)
Starting download: video2.mp4 (30MB)
Starting download: video3.mp4 (20MB)
Completed: video3.mp4 in 2.8s
Starting download: doc1.pdf (5MB)
Completed: doc1.pdf in 0.9s
Starting download: doc2.pdf (3MB)
Completed: video1.mp4 in 3.2s
Starting download: doc3.pdf (8MB)
Completed: doc2.pdf in 0.4s
Starting download: music1.mp3 (12MB)
Completed: video2.mp4 in 4.1s
Starting download: music2.mp3 (15MB)
Completed: doc3.pdf in 1.1s
Starting download: music3.mp3 (10MB)
Completed: music1.mp3 in 1.6s
Completed: music3.mp3 in 1.4s
Completed: music2.mp3 in 2.1s

=== Download Pool Results ===
Total time: 4.2 seconds
Successful downloads: 9/9
Failed downloads: 0
Total data downloaded: 128MB
Average download speed: 30.5 MB/s

ThreadPoolExecutor yuklab olish stsenariylarida worker thread'larni siz uchun boshqaradi. executor.submit() yordamida har bir yuklab olish vazifasi pool'ga topshiriladi va bo'sh worker thread'lar tomonidan concurrent tarzda qayta ishlanadi. Biz max_workers =3 qiymatini belgiladik. Yuklab olishlar thread'lar o'rtasida samarali taqsimlanadi.

Bu bir nechta thread'ni qo'lda ishga tushirish va join qilishdagi boilerplate koddan xalos qiladi hamda batch fayl yuklab olishlar yoki API chaqiruvlari kabi vazifalar uchun ideal. Pool worker thread'lar bo'ylab thread chaqiruvlarini samarali boshqaradi.

Buni sinab ko'ring: max_workers=3. bilan ThreadPoolExecutor'ga 20 ta yuklab olish vazifasini yuboring. Har bir vazifani qaysi worker bajarayotganini ko'rish uchun print statement'lar qo'shing. Vazifalar har doim bir xil worker thread'larga tushadimi?

Endi siz thread'larni qanday yaratish, lock'lar yordamida mutable obyektlar bilan xavfsiz ishlash hamda I/O-bound vazifalar uchun thread pool'lar va daemon thread'lardan foydalanishni ko'rdingiz. Keyingi qadam — thread'lar qachon to'g'ri vosita ekanini va qachon boshqa concurrency modellari sizga yaxshiroq xizmat qilishi mumkinligini tushunish.

Python multithreading'ning amaliy qo'llanilishi

Tarmoq trafigini boshqarayotgan, foydalanuvchi so'rovlarini qayta ishlayotgan yoki fon vazifalarini muvofiqlashtirayotgan bo'lsangiz ham, Python'dagi multithreading kompyuter dasturiga sezgir va samarali bo'lib qolishga yordam beradi.

Quyida bitta Python thread'i yoki ko'plab worker thread'lar unumdorlikni sezilarli darajada oshiradigan eng keng tarqalgan real hayotdagi qo'llanilish sohalari keltirilgan.

Web scraping va ma'lumot yig'ish

Ko'plab veb-sahifalardan ma'lumot to'plashda bitta thread bir vaqtda bitta sahifani so'rashi va har bir javobni kutishi kerak. Multithreading bilan Python dasturi bir nechta so'rovni parallel yubora oladi.

Bitta thread server javobini kutayotganda, boshqa thread'lar ishni davom ettiradi. Bu umumiy scraping vaqtini qisqartiradi va katta hajmdagi ma'lumot yig'uvchilarga ishlarni tezroq yakunlashga yordam beradi. Bu yerda thread bajarilishi ayniqsa foydali, chunki vaqtning katta qismi tarmoq operatsiyalarini kutishga ketadi.

Tarmoq va chat ilovalari

Tarmoq ilovalari ko'pincha bir vaqtning o'zida bir nechta foydalanuvchi bilan ishlaydi. Mijoz xabar yuborganda, server main thread bo'sh qolishi uchun o'sha so'rovni alohida thread'da qayta ishlaydi. Bu boshqa thread'larga yangi mijozlar, fon yangilanishlari yoki chiquvchi xabarlarni qayta ishlash imkonini beradi.

Worker thread'lar ko'plab ulanishlar orqali doimiy aloqa amalga oshadigan chat serverlari, multiplayer o'yinlar va IoT tizimlarida keng tarqalgan.

Fayllarni o'qish va yozish ishlari

Ko'plab fayllar bilan ishlaydigan dasturlar — masalan, log processor'lar, media yuklab oluvchilar yoki ma'lumot import qilish vositalari — multithreading'dan foyda ko'radi, chunki fayl operatsiyalari ko'pincha sekin bo'ladi va disk yoki tarmoq javoblariga bog'liq. Ikki yoki undan ortiq thread'dan foydalanish Python dasturiga turli fayllarni bir vaqtda o'qish yoki saqlash imkonini beradi.

Barcha thread'lar bir xil xotira maydonini ulashsa-da, har bir thread ma'lum bir faylga e'tibor qarata oladi, bu esa workflow'ni silliqroq qiladi va umumiy qayta ishlash vaqtini qisqartiradi.

API'lar va tashqi xizmatlar bilan ishlash

Zamonaviy tizimlar ma'lumotlar bazalari, to'lov provayderlari, machine learning modellari va cloud xizmatlariga API chaqiruvlariga katta darajada tayanadi. Bir vaqtda bitta API chaqiruvini amalga oshirish uzoq kutishlarga olib keladi, thread'lar esa bir nechta so'rovni parallel bajarishga imkon beradi.

Thread instance'i har bir API so'rovini qayta ishlashi mumkin, boshqa thread'lar esa ishlashda davom etadi. Bu throughput'ni oshiradi va Python dasturiga main thread'ni bloklamasdan natijalarni tezroq qaytarishga yordam beradi.

GUI'larni sezgir holda saqlash

Grafik foydalanuvchi interfeyslari (GUI'lar) fayllarni yuklash yoki loyihalarni saqlash kabi og'ir operatsiyalarni bajarayotganda ham faol qolishi kerak. Hamma narsani bitta thread'da bajarish interfeysni qotirib qo'yishi mumkin, natijada ilova buzilgandek tuyuladi. Fon vazifalarini alohida thread'ga ko'chirish orqali GUI kiritishlarni qabul qilish, oynalarni yangilash va foydalanuvchilarga javob berishda davom etadi.

Bu yondashuv matn muharrirlari, dizayn vositalari va ilmiy ilovalarda qo'llaniladi. Daemon thread ko'pincha ilovaning yopilishiga to'sqinlik qilmasdan fon ishlarini qo'llab-quvvatlaydi.

O'yin ishlab chiqish va real vaqtdagi o'zaro ta'sir

O'yinlar bir vaqtning o'zida bir nechta tizimni ishga tushiradi: grafika, fizika, input'ni qayta ishlash, audio va AI. Multithreading bu vazifalarni taqsimlashga yordam beradi, shunda main thread silliq ishlashda davom etadi. Bitta thread o'yinchining harakatlarini boshqarayotganda, boshqa thread o'yin dunyosini yangilashi, yana biri esa ovoz yoki tarmoq ma'lumotlarini qayta ishlashi mumkin.

Python bytecode bajarilishi uchun bitta Python thread'iga tayansa-da, I/O yoki kutish bosqichlarida thread'lar o'rtasida almashish o'yin jarayonini sezgir saqlaydi.

Ma'lumotlar bazasi bilan ishlash va transaction'larni boshqarish

Katta ilovalar ko'pincha bir vaqtning o'zida ko'plab query'larni bajarishi kerak bo'ladi. Bitta query tugashini kutish o'rniga, multithreading dasturga bir nechta query'ni concurrent tarzda yuborish imkonini beradi. Har bir chaqiruvchi thread o'z natijasini kutadi, boshqa thread'lar esa ishlashda davom etadi.

Bu ma'lumotlar bazasi throughput'ini yaxshilaydi, bottleneck'larni kamaytiradi va tizim bilan ko'plab foydalanuvchilar ishlaganda yaxshiroq masshtablanadi.

Audio, video va media qayta ishlash

Media ilovalari ko'pincha kichik vazifalarni takroran bajaradi: frame'larni encode qilish, audio namunalarni o'qish, stream'larni buffering qilish yoki segmentlarni yuklab olish. Barcha thread'larni bitta ketma-ketlikda ishga tushirish hamma narsani sekinlashtirib yuboradi.

Multithreading bilan buffering, rendering va input'ni qayta ishlash kabi vazifalar parallel thread'larda ishlashi mumkin, bu esa dasturiy ta'minotga real vaqtda ijro etishni saqlab qolish imkonini beradi. Bu kontentni ko'rish, tahrirlash yoki stream qilishni silliqroq qiladi.

Python multithreading'dagi keng tarqalgan xatolar va best practice'lar

Python'dagi multithreading kuchli vosita bo'lishi mumkin, lekin u osongina e'tibordan chetda qoladigan nozik xatolarni ham keltirib chiqaradi, ayniqsa ikki yoki undan ortiq thread ma'lumotlarni ulashganda yoki bir-biri bilan aloqa qilganda. Ushbu keng tarqalgan xatolarni tushunish Python'da xavfsizroq, tezroq va oldindan bashorat qilinadigan multithreading kodini yozishga yordam beradi.

Quyida dasturchilar eng ko'p duch keladigan muammolar va har qanday Python dasturida thread'larning silliq bajarilishini ta'minlaydigan best practice'lar keltirilgan.

1-xato: Thread'lar barcha vazifalar uchun parallel ishlaydi deb o'ylash

Keng tarqalgan noto'g'ri tushuncha — CPU-heavy ishlarda bitta Python thread'i boshqa thread'lar bilan bir vaqtda ishlaydi deb ishonish. CPython Global Interpreter Lock'dan foydalangani sababli, bir vaqtda faqat bitta thread Python bytecode'ini bajaradi.

Bu thread'larni I/O vazifalari uchun samarali qiladi, lekin sof hisoblash talab qiladigan vazifalar uchun emas. CPU unumdorligi muhim bo'lganda, odatda alohida process'larga o'tish yaxshiroq tanlov hisoblanadi.

Best practice: I/O-bound workload'lar uchun thread'lardan, CPU-bound vazifalar uchun multiprocessing'dan foydalaning.

2-xato: Chaqirishni unutish

Dasturchilar ko'pincha thread'larni ishga tushiradi, lekin ular tugashini kutish uchun join method'idan foydalanishni unutadi. Bu worker thread'lar hali ishlayotgan paytda main thread'ning erta chiqib ketishiga sabab bo'ladi. Ba'zi hollarda butun kompyuter dasturi barcha thread'lar o'z ishini tugatmasdan oldin yakunlanadi, bu esa tugallanmagan yuklab olishlar, yo'qolgan natijalar yoki buzilgan chiqish ma'lumotlariga olib keladi.

Best practice: Agar thread ataylab daemon thread qilinmagan bo'lsa, har bir thread uchun doim join() ni chaqiring.

3-xato: Umumiy xotiraga xavfsiz bo'lmagan murojaat

Ikki yoki undan ortiq thread bir xil xotira maydoniga himoyasiz murojaat qilganda race condition'lar yuzaga keladi. Klassik misol — umumiy ichki hisoblagichni oshirish. Operatsiya oddiy ko'rinsa-da, har bir increment bir nechta bytecode qadamini o'z ichiga oladi va thread'lar bu qadamlarni oldindan aytib bo'lmaydigan tarzda bir-biriga aralashtirib yuborishi mumkin.

Best practice: Umumiy ma'lumotlarni Lock, RLock yoki event obyekti yordamida himoyalang. Bu synchronization vositalari har bir thread critical section'ni boshqa thread unga kirishidan oldin yakunlashini ta'minlaydi.

4-xato: Mutable obyektlardan synchronization'siz foydalanish

Dictionary'lar, list'lar yoki maxsus obyektlar kabi mutable obyektlarni thread'lar o'rtasida ulashish nozik va izchil bo'lmagan xatti-harakatlarga sabab bo'ladi. Hatto bitta thread zararsiz qiymatlarni o'zgartirayotgandek ko'rinsa ham, boshqa thread'lar bir vaqtning o'zida o'qishi yoki yozishi mumkin, bu esa natijalarni buzadi.

Best practice: Imkon qadar thread target'iga immutable qiymatlarni uzating. Agar mutable tuzilmalar zarur bo'lsa, barcha o'zgartirishlarni thread-safe mexanizmlar ichiga o'rang.

5-xato: Thread lifecycle'i va nomlashni e'tiborsiz qoldirish

Python thread tizimini debugging qilayotganda, agar har bir thread bir xil ko'rinsa, qaysi thread nima qilayotganini kuzatish qiyin. Thread'ga nom berilmasa, log'lar chalkash bo'lib qoladi va bloklangan yoki noto'g'ri ishlayotgan thread'larni aniqlash qiyinlashadi. Dasturchilar, ayniqsa callback'lar ishtirok etganda, joriy thread chaqiruvchi thread'dan farq qilishi mumkinligini ham unutib qo'yishadi.

Best practice: Thread'larga aniq nomlar bering va debugging uchun thread nomini log'ga yozing. Shunda ishlab chiqish jarayonida thread'larni boshqarish va thread chaqiruvlarini kuzatish ancha osonlashadi.

6-xato: Thread pool'lar o'rniga thread'larni haddan tashqari ko'p ishlatish

Juda ko'p thread yaratish thread bajarilishini sekinlashtiradi va tizim resurslarini behuda sarflaydi. Har bir thread xotira maydonini egallaydi va operatsion tizim tomonidan muvofiqlashtirishni talab qiladi. O'nlab yoki yuzlab thread'lar qo'lda yaratilganda, Python dasturi beqaror bo'lib qoladi.

Best practice: Thread'larni samarali boshqarish uchun ThreadPoolExecutor'dan foydalaning. Thread pool'lar worker thread'lardan qayta foydalanadi, overhead'ni kamaytiradi va thread'lar sonining nazoratsiz ko'payib ketishining oldini oladi.

7-xato: Daemon thread'larni noto'g'ri boshqarish

Daemon thread'lar main thread tugaganda avtomatik to'xtaydi. Bu fon ishlarining jimgina bajarilishiga yordam beradi, lekin yangi boshlovchilar ba'zan natijalarni saqlash yoki fayllarni yopish kabi muhim vazifalarni daemon thread'larga joylashtiradi. Agar main thread erta chiqib ketsa, daemon thread darhol to'xtaydi va ishni tugallanmagan holda qoldiradi.

Best practice: Daemon thread'lardan faqat heartbeat signallari yoki davriy tekshiruvlar kabi muhim bo'lmagan fon vazifalari uchun foydalaning.

8-xato: Thread'larga process'lar kabi munosabatda bo'lish

Ba'zi dasturchilar Python'dagi multithreading multiprocessing kabi ishlashini kutishadi. Lekin thread'lar bitta xotira maydonini ulashadi, alohida process'lar esa yo'q. Bu umumiy maydon thread'larni yengilroq va tezroq qiladi, lekin thread'lar bir vaqtning o'zida bir xil ma'lumotga murojaat qilganda xavfni ham oshiradi.

Best practice: Vazifalar ma'lumotlarni ulashsa va muvofiqlashtirishni talab qilsa, thread'larni tanlang; izolyatsiyalangan xotira yoki haqiqiy CPU parallelism'i kerak bo'lganda process'larni tanlang.

9-xato: Queue'ga asoslangan aloqani unutish

Yangi boshlovchilar ko'pincha umumiy obyektlarni thread'lar o'rtasida qo'lda uzatishadi, bu esa xatolarga olib keladi. Xavfsizroq variant — thread-safe queue'dan foydalanish, shunda har bir consumer thread boshqalar bilan to'qnashmasdan vazifalarni oladi.

Best practice: Thread'lar o'rtasida ish elementlarini yuborishda queue.Queue() dan foydalaning. U locking'ni ichkarida boshqaradi va oldindan bashorat qilinadigan aloqani ta'minlaydi.

10-xato: Thread'lardagi xatolarni qayta ishlashga e'tibor bermaslik

Agar thread target'i exception ko'tarsa, u jimgina muvaffaqiyatsiz tugashi mumkin, bu esa Python dasturining oldindan aytib bo'lmaydigan tarzda ishlashiga sabab bo'ladi. Dasturchilar thread odatdagidek tugadi deb o'ylashadi, lekin xato fonda yutib yuborilgan bo'ladi.

Best practice: Thread target'larini try/except bloklariga o'rang yoki exception'larni ushlab, ularni toza tarzda qaytaradigan thread pool'lardan foydalaning.

Xulosa

Ajoyib ish! Siz thread'larni yaratish, sinxronlash va ular o'rtasida ma'lumotlarni xavfsiz ulashishni o'rgandingiz, producer-consumer va ThreadPoolExecutor kabi ilg'or pattern'larni mashq qildingiz hamda workload'ga qarab qachon thread'lar, multiprocessing yoki async'dan foydalanish kerakligini ko'rib chiqdingiz.

Qo'llanma davomida sizga AI tutor uchun prompt'lar berildi; u endi sizni statik darslardan tashqariga olib chiqish uchun barcha qo'llanmalarda mavjud. Biroq bu aqlli o'qituvchimiz imkoniyatlaridan foydalanishning yagona yo'li emas. Uning maxsus sahifasini ko'rib chiqing. U yerda Python bo'yicha bilim darajangizni belgilashingiz, o'quv maqsadlaringizni aniqlashingiz va bajarmoqchi bo'lgan vazifalar turlarini tanlashingiz mumkin. Shularga asoslanib, u moslashtirilgan topshiriqlar, real vaqtdagi fikr-mulohazalar va code review'lar bilan shaxsiylashtirilgan kurs yoki roadmap tuzib beradi. U sizning rivojlanishingizga moslashadi, murakkab race condition'larni debug qilishga yordam beradi va real tizimlardan olingan stsenariylarni taqdim etadi.

Ta'limdagi AI qiziqtiradimi? Bugunoq AI Tutor'imiz bilan tanishing va o'rganish yo'lingizni keyingi bosqichga olib chiqing

Yo'l xaritalariQo'llanmalarFAQYouTube

yo'lxaritaroadmap.shasosida

Hamjamiyat tomonidan yaratilgan yo'l xaritalari, eng yaxshi amaliyotlar, loyihalar, maqolalar, manbalar va yo'nalishlar — o'z yo'lingizni tanlash va kasbingizda o'sish uchun.

© yo'lxarita·Shartlar·Maxfiylik·

roadmap.sh

Barcha kontent roadmap.sh loyihasidan olingan (© Kamran Ahmed) va shaxsiy o'rganish uchun o'zbek tiliga tarjima qilinmoqda.

Qidiruv

Yo'l xaritalari, loyihalar va savollarni qidiring