مدیریت خطا در threading

Please login to bookmark Close

در این فصل، به‌صورت کامل و مستقل به بررسی مدیریت خطا در محیط threading می‌پردازیم. فرض می‌کنیم که شما با مفاهیم پایه‌ی threading آشنایی دارید و قصد دارید برنامه‌هایی مقاوم و قابل‌اعتماد با استفاده از تردها بنویسید.

چالش‌های منحصربه‌فرد threading در مدیریت خطا

threading در پایتون، با وجود سادگی، چالش‌های خاص خود را در مدیریت خطا دارد. بیایید ابتدا این چالش‌ها را بشناسیم:

۱. عدم امکان قطع اجباری ترد

برخلاف asyncio که می‌توانید یک تسک را با cancel() متوقف کنید، در threading هیچ راه‌کار توکاری برای متوقف کردن یک ترد از بیرون وجود ندارد. متد thread.join(timeout) فقط منتظر می‌ماند و پس از اتمام زمان، اجرا را ادامه می‌دهد، اما خود ترد همچنان به کار خود ادامه می‌دهد.

۲. اشتراک‌گذاری حافظه

تردها در پایتون حافظه‌ی مشترک دارند. این ویژگی باعث می‌شود خطاهای مربوط به شرایط رقابتی (Race Condition) و تخریب داده‌ها، در threading شایع‌تر از سایر رویکردها باشند.

۳. مدیریت استثناها

اگر یک استثنا در یک ترد رخ دهد و مدیریت نشود، آن ترد از کار می‌افتد، اما سایر تردها و برنامه‌ی اصلی به کار خود ادامه می‌دهند. این یعنی ممکن است خطا به‌طور کامل نادیده گرفته شود.

۴. تشخیص وضعیت تردها

بررسی اینکه آیا یک ترد هنوز در حال اجراست یا متوقف شده، نیازمند استفاده از متد is_alive() است. اما این متد اطلاعاتی درباره‌ی علت توقف ترد (پایان عادی یا خطا) به ما نمی‌دهد.

الگوی Timeout در threading

همان‌طور که گفته شد، قطع کردن اجباری یک ترد در پایتون ممکن نیست. اما روش‌های مختلفی برای شبیه‌سازی Timeout و مدیریت عملیات‌های طولانی وجود دارد.

روش اول: استفاده از threading.Timer

سناریوی واقعی: فرض کنید در یک برنامه‌ی پردازش تصویر، یک ترد را برای اعمال فیلتر روی یک تصویر بزرگ راه‌اندازی کرده‌اید. اگر این عملیات بیش از ۱۰ ثانیه طول بکشد، می‌خواهید به کاربر اطلاع دهید و اجازه دهید تصمیم بگیرد که آیا منتظر بماند یا عملیات را لغو کند.

import threading
import time

def apply_image_filter():
    print("applying filter to image...")
    time.sleep(10)
    print("filter applied successfully")

def timeout_handler():
    print("timeout occurred! image processing is taking too long.")
    print("you can choose to wait or cancel the operation.")

timer = threading.Timer(5.0, timeout_handler)
timer.start()

thread = threading.Thread(target=apply_image_filter)
thread.start()
thread.join()

if thread.is_alive():
    print("thread is still running despite timeout")
else:
    print("thread finished")

timer.cancel()

نکته‌ی مهم: همان‌طور که در خروجی می‌بینید، Timer فقط اعلام می‌کند که زمان به پایان رسیده، اما ترد اصلی همچنان به کار خود ادامه می‌دهد. اگر می‌خواهیم عملیات را واقعاً متوقف کنیم، باید از روش‌های هماهنگی استفاده کنیم.

روش دوم: استفاده از threading.Event برای هماهنگی

سناریوی واقعی: یک برنامه‌ی دانلود فایل دارید که فایل‌های بزرگ را از سرور دریافت می‌کند. اگر سرعت اینترنت پایین باشد، دانلود ممکن است بیش از حد طول بکشد. می‌خواهید اگر دانلود بیش از ۳۰ ثانیه طول کشید، به کاربر امکان لغو دانلود را بدهید.

import threading
import time

def download_file(stop_event):
    print("download started")
    for chunk in range(10):
        if stop_event.is_set():
            print("download cancelled by user")
            return "cancelled"
        time.sleep(1)
        print(f"downloaded chunk {chunk + 1}/10")
    print("download completed successfully")
    return "file_content"

stop_event = threading.Event()
thread = threading.Thread(target=download_file, args=(stop_event,))
thread.start()

time.sleep(3)
user_choice = input("download taking too long. Cancel? (y/n): ")
if user_choice.lower() == 'y':
    stop_event.set()
    print("cancellation signal sent")

thread.join()
print("thread finished")

مزایا:

  • ترد به‌صورت خودکار و ایمن متوقف می‌شود.
  • هیچ منبعی به‌صورت اجباری آزاد نمی‌شود.
  • امکان انجام عملیات پاک‌سازی قبل از خروج وجود دارد.

معایب:

  • نیاز به تغییر کد تابع برای چک کردن دوره‌ای Event دارد.
  • در عملیات‌های blocking (مانند socket.recv) نمی‌توان از این روش استفاده کرد.

روش سوم: استفاده از concurrent.futures.ThreadPoolExecutor

سناریوی واقعی: یک API سرویس پرداخت آنلاین دارید که باید نتیجه‌ی تراکنش را در کمتر از ۲ ثانیه برگرداند. اگر سرویس پرداخت کند کار کند یا قطع شود، باید به کاربر اعلام کنید که تراکنش ناموفق بوده و دوباره تلاش کند.

from concurrent.futures import ThreadPoolExecutor, TimeoutError
import time
import random

def payment_gateway_api():
    print("connecting to payment gateway...")
    time.sleep(random.uniform(0.5, 3.0))
    if random.random() < 0.2:
        raise ValueError("payment gateway returned error")
    return "transaction_successful"

def process_payment(timeout_seconds=2.0):
    with ThreadPoolExecutor(max_workers=1) as executor:
        future = executor.submit(payment_gateway_api)
        try:
            result = future.result(timeout=timeout_seconds)
            print(f"payment result: {result}")
            return result
        except TimeoutError:
            print("payment gateway timed out - transaction pending")
            future.cancel()
            print("please check transaction status later")
            return "pending"
        except Exception as e:
            print(f"payment failed: {e}")
            return "failed"

result = process_payment(2.0)
print(f"final status: {result}")

نکته: future.cancel() در ThreadPoolExecutor تضمینی برای توقف واقعی ترد نیست. اگر ترد در حال اجرا باشد، cancel() تأثیری ندارد. اما اگر ترد هنوز شروع نشده باشد، از اجرای آن جلوگیری می‌کند.

روش چهارم: ترکیب Event و ThreadPoolExecutor

سناریوی واقعی: یک ربات معاملاتی خودکار دارید که باید قیمت لحظه‌ای ارزهای دیجیتال را از چندین صرافی دریافت کند. هر صرافی ممکن است کند پاسخ دهد. می‌خواهید اگر یک صرافی بیش از ۱ ثانیه پاسخ نداد، از آن صرف نظر کنید و از سایر صرافی‌ها استفاده کنید.

from concurrent.futures import ThreadPoolExecutor, TimeoutError
import threading
import time
import random

def fetch_exchange_price(exchange_name, stop_event):
    print(f"fetching price from {exchange_name}...")
    for step in range(5):
        if stop_event.is_set():
            print(f"price fetch from {exchange_name} cancelled")
            return None
        time.sleep(0.3)
    if random.random() < 0.3:
        time.sleep(2.0)
    return f"{exchange_name}_price_$100"

def get_market_prices(max_wait=1.0):
    exchanges = ["binance", "coinbase", "kraken"]
    stop_event = threading.Event()
    prices = {}

    with ThreadPoolExecutor(max_workers=len(exchanges)) as executor:
        futures = {
            executor.submit(fetch_exchange_price, exchange, stop_event): exchange
            for exchange in exchanges
        }

        for future in futures:
            exchange = futures[future]
            try:
                price = future.result(timeout=max_wait)
                if price:
                    prices[exchange] = price
            except TimeoutError:
                print(f"timeout getting price from {exchange}")
                stop_event.set()

    return prices

prices = get_market_prices(1.0)
print(f"market prices: {prices}")

الگوی Retry در threading

پیاده‌سازی Retry در threading بسیار شبیه به حالت عادی است، اما به‌جای asyncio.sleep از time.sleep استفاده می‌کنیم.

Retry با تأخیر ثابت (Fixed Delay)

سناریوی واقعی: یک برنامه‌ی ارسال ایمیل انبوه دارید. گاهی سرور ایمیل به‌دلیل شلوغی، خطای موقت برمی‌گرداند. می‌خواهید در صورت بروز خطای موقت، تا ۳ بار با فاصله‌ی ۲ ثانیه دوباره تلاش کنید.

import time
import random

def send_email(recipient):
    print(f"sending email to {recipient}...")
    if random.random() < 0.6:
        raise ConnectionError("smtp server temporarily unavailable")
    print(f"email sent to {recipient} successfully")
    return "email_sent"

def retry_fixed_delay(operation, *args, max_retries=3, delay=2.0):
    for attempt in range(max_retries):
        try:
            print(f"attempt {attempt + 1} for {args[0]}")
            result = operation(*args)
            return result
        except Exception as e:
            print(f"attempt {attempt + 1} failed: {e}")
            if attempt == max_retries - 1:
                print(f"all attempts failed for {args[0]}")
                raise
            print(f"waiting {delay} seconds before retry...")
            time.sleep(delay)

recipients = ["user1@example.com", "user2@example.com"]
for recipient in recipients:
    try:
        result = retry_fixed_delay(send_email, recipient, max_retries=3, delay=2.0)
        print(f"final result for {recipient}: {result}")
    except Exception as e:
        print(f"email to {recipient} failed completely: {e}")

Retry با تأخیر تصاعدی (Exponential Backoff)

سناریوی واقعی: یک سرویس تحلیل داده‌های مالی دارید که هر ساعت، داده‌های جدید را از یک API خارجی دریافت می‌کند. API خارجی گاهی با حجم درخواست‌ها سنگین می‌شود. با Exponential Backoff، در صورت خطا، مدت انتظار را افزایش می‌دهید تا به سرور فشار نیاورید.

import time
import random

def fetch_financial_data():
    print("fetching financial data...")
    if random.random() < 0.5:
        raise ConnectionError("data provider service busy")
    time.sleep(0.5)
    return {"price": 100, "volume": 1000}

def retry_exponential_backoff(operation, max_retries=5, base_delay=1.0):
    for attempt in range(max_retries):
        try:
            print(f"attempt {attempt + 1} started")
            result = operation()
            print(f"attempt {attempt + 1} succeeded")
            return result
        except Exception as e:
            print(f"attempt {attempt + 1} failed: {e}")
            if attempt == max_retries - 1:
                print("all attempts failed - service might be down")
                raise

            delay = base_delay * (2 ** attempt)
            jitter = random.uniform(0, delay * 0.2)
            total_delay = delay + jitter
            print(f"waiting {total_delay:.2f} seconds before retry...")
            time.sleep(total_delay)

try:
    data = retry_exponential_backoff(fetch_financial_data, max_retries=4)
    print(f"final data: {data}")
except Exception as e:
    print(f"data fetch failed: {e}")

Retry با شرط (Conditional Retry)

سناریوی واقعی: یک سیستم پردازش سفارش در فروشگاه اینترنتی دارید. اگر خطای مربوط به موجودی کالا رخ دهد (کالا تمام شده)، دیگر تلاش مجدد فایده‌ای ندارد. اما اگر خطای شبکه یا خطای موقت سرور باشد، می‌توانید دوباره تلاش کنید.

import time
import random

class RetryableError(Exception):
    pass

class FatalError(Exception):
    pass

def process_order(order_id):
    rand = random.random()
    if rand < 0.3:
        raise RetryableError("payment gateway temporarily unavailable")
    elif rand < 0.6:
        raise FatalError("product out of stock")
    return f"order_{order_id}_processed"

def retry_on_specific_errors(operation, *args, max_retries=3):
    for attempt in range(max_retries):
        try:
            print(f"attempt {attempt + 1} for order {args[0]}")
            result = operation(*args)
            print(f"attempt {attempt + 1} succeeded")
            return result
        except RetryableError as e:
            print(f"retryable error on attempt {attempt + 1}: {e}")
            if attempt == max_retries - 1:
                print("max retries reached, marking order as pending")
                raise
            delay = 1.0 * (2 ** attempt)
            time.sleep(delay)
        except FatalError as e:
            print(f"fatal error, stopping retry: {e}")
            print("order needs manual intervention")
            raise
        except Exception as e:
            print(f"unexpected error: {e}")
            raise

orders = ["ORD-1001", "ORD-1002", "ORD-1003"]
for order in orders:
    try:
        result = retry_on_specific_errors(process_order, order, max_retries=3)
        print(f"order {order}: {result}")
    except FatalError:
        print(f"order {order}: failed permanently")
    except RetryableError:
        print(f"order {order}: pending - will retry later")
    except Exception as e:
        print(f"order {order}: unexpected error: {e}")

ترکیب Timeout و Retry در threading

سناریوی واقعی: یک اپلیکیشن موبایل دارید که با سرور مرکزی همگام‌سازی می‌شود. هر بار همگام‌سازی، باید اطلاعات کاربر را از سرور دریافت کند. سرور گاهی کند می‌شود یا قطع می‌شود. می‌خواهید تا ۳ بار تلاش کنید و هر بار حداکثر ۲ ثانیه صبر کنید.

from concurrent.futures import ThreadPoolExecutor, TimeoutError
import time
import random

def sync_with_server():
    delay = random.uniform(0.5, 4.0)
    print(f"sync will take {delay:.2f} seconds")
    time.sleep(delay)

    if random.random() < 0.3:
        raise ValueError("server returned invalid data")
    return "user_data_synced"

def robust_sync(max_retries=3, timeout_per_attempt=2.0):
    for attempt in range(max_retries):
        print(f"\nsync attempt {attempt + 1} started")

        with ThreadPoolExecutor(max_workers=1) as executor:
            future = executor.submit(sync_with_server)
            try:
                result = future.result(timeout=timeout_per_attempt)
                print(f"attempt {attempt + 1} succeeded")
                return result
            except TimeoutError:
                print(f"attempt {attempt + 1} timed out")
                future.cancel()
            except Exception as e:
                print(f"attempt {attempt + 1} failed with error: {e}")
                future.cancel()

            if attempt == max_retries - 1:
                print("all sync attempts failed")
                raise Exception("sync failed after all retries")

            delay = 0.5 * (2 ** attempt)
            print(f"waiting {delay} seconds before next attempt...")
            time.sleep(delay)

try:
    result = robust_sync(max_retries=4, timeout_per_attempt=2.0)
    print(f"sync completed: {result}")
except Exception as e:
    print(f"sync failed: {e}")
    print("showing cached data to user...")

مدیریت استثناها در تردها

یکی از چالش‌های مهم در threading، مدیریت استثناهایی است که در تردها رخ می‌دهند. اگر استثنا در ترد مدیریت نشود، آن ترد از کار می‌افتد اما برنامه‌ی اصلی متوقف نمی‌شود.

روش اول: استفاده از threading.excepthook

سناریوی واقعی: یک اپلیکیشن مانیتورینگ دارید که ۱۰۰ ترد برای بررسی وضعیت سرورهای مختلف اجرا می‌کند. اگر یکی از تردها به‌دلیل خطای غیرمنتظره از کار بیفتد، می‌خواهید بدانید و خطا را لاگ کنید تا بتوانید مشکل را ردیابی کنید.

import threading
import time
import logging

logging.basicConfig(level=logging.INFO)

def exception_handler(args):
    thread_name = args.thread.name
    exc_type = args.exc_type.__name__
    exc_value = args.exc_value
    print(f"ERROR in thread '{thread_name}': {exc_type}: {exc_value}")
    logging.error(f"Thread '{thread_name}' crashed: {exc_type}: {exc_value}")

threading.excepthook = exception_handler

def monitor_server(server_name):
    print(f"monitoring {server_name}...")
    time.sleep(1)
    if server_name == "server-3":
        raise ValueError(f"unexpected response from {server_name}")
    print(f"{server_name} is healthy")

servers = ["server-1", "server-2", "server-3", "server-4"]
threads = []

for server in servers:
    thread = threading.Thread(target=monitor_server, args=(server,))
    thread.name = f"Monitor-{server}"
    thread.start()
    threads.append(thread)

for thread in threads:
    thread.join()

print("monitoring cycle completed")

روش دوم: برگرداندن استثنا با Queue

سناریوی واقعی: یک سیستم پردازش تصویر دارید که چندین تصویر را همزمان پردازش می‌کند. اگر پردازش یک تصویر با خطا مواجه شود، می‌خواهید نتیجه‌ی آن را به ترد اصلی گزارش دهید تا مشخص شود کدام تصویر پردازش نشده است.

import threading
import time
import queue

def process_image(image_path, result_queue):
    try:
        print(f"processing {image_path}...")
        time.sleep(1)
        if "corrupted" in image_path:
            raise ValueError(f"image {image_path} is corrupted")
        if "large" in image_path:
            raise MemoryError(f"image {image_path} is too large")
        result_queue.put(f"processed_{image_path}")
    except Exception as e:
        result_queue.put(e)

def batch_process_images(image_paths):
    result_queue = queue.Queue()
    threads = []
    results = {}

    for path in image_paths:
        thread = threading.Thread(
            target=process_image, 
            args=(path, result_queue)
        )
        thread.start()
        threads.append(thread)

    for thread in threads:
        thread.join()

    while not result_queue.empty():
        result = result_queue.get()
        if isinstance(result, Exception):
            print(f"error during processing: {result}")
        else:
            print(f"success: {result}")
            results[result] = "processed"

    return results

images = [
    "image1.jpg", 
    "image2_corrupted.jpg", 
    "image3.jpg", 
    "image4_large.jpg"
]
results = batch_process_images(images)
print(f"processed {len(results)} images successfully")

روش سوم: استفاده از concurrent.futures برای دریافت استثنا

سناریوی واقعی: یک ربات تلگرام دارید که همزمان به چندین کاربر پاسخ می‌دهد. اگر پاسخ به یک کاربر با خطا مواجه شود، نباید روی پاسخ به سایر کاربران تأثیر بگذارد. با استفاده از Future، استثناها به‌طور خودکار به ترد اصلی منتقل می‌شوند و می‌توانید تصمیم بگیرید که چه کاری انجام دهید.

from concurrent.futures import ThreadPoolExecutor
import time
import random

def handle_user_request(user_id):
    print(f"handling request from user {user_id}...")
    time.sleep(random.uniform(0.5, 1.5))

    if user_id == 1002:
        raise ValueError(f"user {user_id} has invalid token")
    if user_id == 1005:
        raise ConnectionError(f"cannot connect to user {user_id}")

    return f"response_for_user_{user_id}"

def process_users(user_ids):
    with ThreadPoolExecutor(max_workers=3) as executor:
        futures = {
            executor.submit(handle_user_request, user_id): user_id
            for user_id in user_ids
        }

        results = {}
        for future in futures:
            user_id = futures[future]
            try:
                result = future.result()
                results[user_id] = result
                print(f"user {user_id} handled successfully")
            except Exception as e:
                print(f"error handling user {user_id}: {e}")
                results[user_id] = f"error: {e}"

    return results

users = [1001, 1002, 1003, 1004, 1005, 1006]
results = process_users(users)

for user_id, result in results.items():
    print(f"user {user_id}: {result}")

شبیه‌سازی Idempotency در threading

سناریوی واقعی: یک سیستم پردازش تراکنش بانکی دارید. اگر یک تراکنش به‌دلیل خطای شبکه با شکست مواجه شود، سیستم به‌طور خودکار آن را دوباره اجرا می‌کند. اما اگر تراکنش قبلاً انجام شده باشد، نباید دوباره اجرا شود تا از پرداخت دوبار‌ه جلوگیری شود.

import threading
import time
import uuid

class TransactionProcessor:
    def __init__(self):
        self.lock = threading.Lock()
        self.processed_transactions = set()

    def process_payment(self, transaction_id, amount, idempotency_key):
        with self.lock:
            if idempotency_key in self.processed_transactions:
                print(f"transaction {transaction_id} already processed (idempotency key: {idempotency_key})")
                return "already_processed"
            else:
                print(f"processing transaction {transaction_id}...")
                time.sleep(0.5)

                if amount > 1000:
                    raise ValueError("amount exceeds daily limit")

                self.processed_transactions.add(idempotency_key)
                print(f"transaction {transaction_id} processed successfully")
                return "success"

def process_with_retry(processor, transaction_id, amount, max_retries=3):
    idempotency_key = f"{transaction_id}_{str(uuid.uuid4())[:8]}"

    for attempt in range(max_retries):
        try:
            result = processor.process_payment(transaction_id, amount, idempotency_key)
            return result
        except Exception as e:
            print(f"attempt {attempt + 1} for transaction {transaction_id} failed: {e}")
            if attempt == max_retries - 1:
                raise
            time.sleep(0.5 * (2 ** attempt))

    return "failed"

processor = TransactionProcessor()

transactions = [
    {"id": "TXN-001", "amount": 500},
    {"id": "TXN-002", "amount": 1500},
    {"id": "TXN-003", "amount": 700}
]

for tx in transactions:
    try:
        result = process_with_retry(processor, tx["id"], tx["amount"], max_retries=3)
        print(f"transaction {tx['id']}: {result}")
    except Exception as e:
        print(f"transaction {tx['id']} failed permanently: {e}")

print("\nretrying transaction TXN-002 with different idempotency key...")
try:
    result = process_with_retry(processor, "TXN-002", 1500, max_retries=2)
    print(f"retry result: {result}")
except Exception as e:
    print(f"retry failed: {e}")

مدیریت چندین ترد و جمع‌آوری نتایج

سناریوی واقعی: یک سیستم Crawler دارید که باید از ۱۰ سایت مختلف، اطلاعات قیمت محصولات را دریافت کند. هر سایت ممکن است کند پاسخ دهد یا با خطا مواجه شود. می‌خواهید در نهایت، نتایج موفق را جمع‌آوری کنید و خطاهای هر سایت را لاگ کنید.

from concurrent.futures import ThreadPoolExecutor, TimeoutError
import threading
import time
import queue
import random

class CrawlerManager:
    def __init__(self, timeout_per_site=2.0, max_retries=2):
        self.timeout = timeout_per_site
        self.max_retries = max_retries
        self.results = {}
        self.errors = {}
        self.lock = threading.Lock()

    def crawl_site(self, site_name):
        print(f"crawling {site_name}...")
        delay = random.uniform(0.3, 3.0)
        time.sleep(delay)

        if random.random() < 0.3:
            raise ConnectionError(f"cannot connect to {site_name}")
        if random.random() < 0.2:
            raise ValueError(f"invalid data from {site_name}")

        return {site_name: "data_content"}

    def crawl_with_timeout(self, site_name):
        for attempt in range(self.max_retries):
            try:
                with ThreadPoolExecutor(max_workers=1) as executor:
                    future = executor.submit(self.crawl_site, site_name)
                    result = future.result(timeout=self.timeout)
                    with self.lock:
                        self.results[site_name] = result
                    return True
            except TimeoutError:
                print(f"timeout crawling {site_name}, attempt {attempt + 1}")
            except Exception as e:
                print(f"error crawling {site_name}, attempt {attempt + 1}: {e}")

            if attempt < self.max_retries - 1:
                time.sleep(0.5 * (2 ** attempt))

        with self.lock:
            self.errors[site_name] = "all retries failed"
        return False

    def crawl_all(self, sites, max_workers=5):
        threads = []

        with ThreadPoolExecutor(max_workers=max_workers) as executor:
            futures = {
                executor.submit(self.crawl_with_timeout, site): site
                for site in sites
            }

            for future in futures:
                site = futures[future]
                try:
                    success = future.result()
                    if success:
                        print(f"{site} crawled successfully")
                    else:
                        print(f"{site} failed after retries")
                except Exception as e:
                    print(f"unexpected error for {site}: {e}")

        return self.results, self.errors

manager = CrawlerManager(timeout_per_site=2.0, max_retries=2)
sites = [
    "site1.com", "site2.com", "site3.com", 
    "site4.com", "site5.com", "site6.com"
]

results, errors = manager.crawl_all(sites, max_workers=3)

print(f"\nsuccessful crawls ({len(results)}):")
for site, data in results.items():
    print(f"  {site}: {data}")

print(f"\nfailed crawls ({len(errors)}):")
for site, error in errors.items():
    print(f"  {site}: {error}")

بهترین شیوه‌ها (Best Practices) در مدیریت خطای threading

۱. همیشه از threading.Event برای سیگنال‌دهی استفاده کنید

به‌جای تلاش برای قطع اجباری تردها، از Event برای هماهنگی استفاده کنید.

  1. از concurrent.futures برای مدیریت آسان‌تر استفاده کنید این کتابخانه، مدیریت Timeout و استثناها را بسیار ساده‌تر می‌کند.
  2. همیشه استثناهای تردها را مدیریت کنید یا با threading.excepthook، یا با استفاده از Queue یا Future، مطمئن شوید که خطاهای تردها نادیده گرفته نمی‌شوند.
  3. برای عملیات‌های طولانی، از چک کردن دوره‌ای Event استفاده کنید ترد شما باید به‌گونه‌ای طراحی شود که به‌صورت دوره‌ای وضعیت Event را بررسی کند.
  4. از Exponential Backoff با Jitter در Retry استفاده کنید این کار از هجوم به سرویس‌های خارجی جلوگیری می‌کند.
  5. اطمینان از Idempotency قبل از استفاده از Retry اگر عملیات Idempotent نیست، برای Retry کردن باید از شناسه‌های یکتا (Idempotency Key) استفاده کنید.
  6. لاگ‌های دقیق نگه دارید در هر مرحله از Timeout و Retry، لاگ بنویسید تا بتوانید مشکلات را ردیابی کنید.
  7. از Thread Pool به‌جای ایجاد تردهای جدید استفاده کنید ThreadPoolExecutor مدیریت بهتری روی منابع دارد و خطاها را به‌صورت یکپارچه‌تری مدیریت می‌کند.

جمع‌بندی

مدیریت خطا در threading نیازمند دقت و توجه ویژه‌ای است. برخلاف asyncio که ابزارهای توکار قدرتمندی برای Timeout و Cancellation دارد، در threading باید از روش‌های هماهنگی مثل Event و concurrent.futures استفاده کنیم.

الگوهای اصلی که در این فصل بررسی کردیم عبارتند از:

  • Timeout: با استفاده از threading.Timer، threading.Event و concurrent.futures
  • Retry: با تأخیر ثابت، تأخیر تصاعدی و شرطی
  • ترکیب Timeout و Retry: برای ساخت برنامه‌های مقاوم در برابر خطا
  • مدیریت استثناها: با threading.excepthook، Queue و Future
  • Idempotency: برای اطمینان از ایمنی Retry کردن

با رعایت این اصول و الگوها، می‌توانید برنامه‌هایی بنویسید که در برابر خطاهای موقت، طولانی شدن عملیات‌ها و شرایط غیرمنتظره، مقاوم باشند.

Please login to bookmark Close
پیشرفت شما در «دوره آموزش کانکارنسی در پایتون» (29%)
نظرات

دیدگاهتان را بنویسید

29%
پیشرفت

سرفصل دوره

فهرست مطالب

سرفصل دوره

تمرین

این قسمت تمرین ندارد!

پاسخ تمرین ها

هنوز برای تمرین‌های این قسمت پاسخی ثبت نشده است!

اشتراک گذاری

چرا بهتره از فیلترشکن استفاده کنید؟

من همه ویدئو ها و پادکست های کُدباز رو توی یوتیوب و ساندکلود و پلتفرم هایی آپلود می‌کنم که اغلب فیلتر هستند.

اغلب آموزش‌ها ویدئو و پادکست دارند. پس اگر می‌خواهید از محتوای سایت بیشترین استفاده رو ببرید نیاز به فیلتر شکن دارید.

توجه داشته باشید که برای خرید از فروشگاه بهتره فیلتر شکن رو خاموش کنید.

تنظیمات

انتخاب زبان
تغییر تم