Python Programming

همزمانی در پایتون برای برنامه‌های مورد نیاز داده: AsyncIO در مقابل چندپردازشی

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

درک مدل‌های همزمانی پایتون

پایتون دو روش اصلی برای همزمانی ارائه می‌دهد: AsyncIO برای عملیات محدود به ورودی/خروجی و چندپردازشی برای وظایف محدود به پردازنده. هر روش مناسب برای اهداف مختلف و در سناریوهای خاصی برجسته است.

AsyncIO: قدرت برنامه‌نویسی ناهمزمان

AsyncIO کتابخانه داخلی پایتون برای نوشتن کد همزمان با استفاده از سینتکس async/await است. این کتابخانه به‌ویژه برای عملیات محدود به ورودی/خروجی که وظایف زمانی را صرف انتظار برای منابع خارجی می‌کنند، مؤثر است.

import asyncio
import aiohttp
import time

async def fetch_url(session, url):
    async with session.get(url) as response:
        return await response.text()

async def fetch_multiple_urls(urls):
    async with aiohttp.ClientSession() as session:
        tasks = [fetch_url(session, url) for url in urls]
        results = await asyncio.gather(*tasks)
        return results

# مثال استفاده
urls = [
    'https://httpbin.org/delay/1',
    'https://httpbin.org/delay/1',
    'https://httpbin.org/delay/1'
]

start_time = time.time()
results = asyncio.run(fetch_multiple_urls(urls))
end_time = time.time()
print(f"Fetched {len(urls)} URLs in {end_time - start_time:.2f} seconds")

برنامه‌نویسی ناهمزمان در سناریوهایی که وظایف در انتظار پاسخ‌های شبکه، پرس‌وجوهای پایگاه داده یا ورودی/خروجی فایل هستند، برجسته است. مزیت کلیدی این است که یک کوروتین می‌تواند کنترل را در حین انتظار از دست بدهد و به کوروتین‌های دیگر اجازه اجرای همزمان دهد.

چندپردازشی: استفاده از چندین هسته

چندپردازشی فرآیندهای جداگانه‌ای از تفسیرگر پایتون ایجاد می‌کند که هر کدام دارای تفسیرگر و فضای حافظه خود هستند. این روش برای عملیات محدود به پردازنده مناسب است که در آن می‌خواهید از چندین هسته پردازنده همزمان استفاده کنید.

import multiprocessing as mp
import time
import math

def cpu_intensive_task(n):
    # شبیه‌سازی کار پردازشی سنگین
    result = 0
    for i in range(n):
        result += math.sqrt(i)
    return result

def process_chunk(data_chunk):
    return [cpu_intensive_task(x) for x in data_chunk]

def parallel_processing_example(data):
    # تقسیم داده به قطعات برای هر فرآیند
    chunk_size = len(data) // mp.cpu_count()
    chunks = [data[i:i + chunk_size] for i in range(0, len(data), chunk_size)]
    
    with mp.Pool() as pool:
        results = pool.map(process_chunk, chunks)
    
    # صاف کردن نتایج
    flattened = [item for sublist in results for item in sublist]
    return flattened

# مثال استفاده
data = list(range(10000, 100000, 1000))
start_time = time.time()
results = parallel_processing_example(data)
end_time = time.time()
print(f"Processed {len(data)} items in {end_time - start_time:.2f} seconds")

وقتی از AsyncIO در مقابل چندپردازشی انتخاب کنیم

تصمیم بین این دو روش بستگی به ماهیت بار کاری شما دارد:

  • AsyncIO در موارد زیر عملکرد برجسته دارد: درخواست‌های شبکه، عملیات پایگاه داده، ورودی/خروجی فایل و هر عملیاتی که برنامه منتظر منابع خارجی است.
  • چندپردازشی در موارد زیر عملکرد برجسته دارد: محاسبات ریاضی، پردازش تصویر، تحلیل داده و الگوریتم‌های سنگین پردازشی.

روش‌های ترکیبی برای برنامه‌های پیچیده

بسیاری از برنامه‌های واقعی از ترکیب این دو روش بهره‌مند می‌شوند:

import asyncio
import multiprocessing as mp
from concurrent.futures import ProcessPoolExecutor
import aiohttp
import time

async def fetch_and_process(session, url):
    # دریافت داده به صورت ناهمزمان
    async with session.get(url) as response:
        data = await response.text()
    
    # پردازش داده با استفاده از چندپردازشی
    with ProcessPoolExecutor() as executor:
        result = executor.submit(cpu_intensive_calculation, data)
        return result.result()

def cpu_intensive_calculation(data):
    # پردازش محدود به پردازنده
    return len(data) ** 2

async def hybrid_example():
    urls = [
        'https://httpbin.org/delay/1',
        'https://httpbin.org/delay/1',
        'https://httpbin.org/delay/1'
    ]
    
    async with aiohttp.ClientSession() as session:
        tasks = [fetch_and_process(session, url) for url in urls]
        results = await asyncio.gather(*tasks)
        return results

# این روش ترکیب بهترین‌ها را از هر دو دارد
async def main():
    start_time = time.time()
    results = await hybrid_example()
    end_time = time.time()
    print(f"Hybrid approach completed in {end_time - start_time:.2f} seconds")
    print(f"Results: {results}")

# asyncio.run(main())

ملاحظات عملکردی و بهترین روش‌ها

هر دو روش تأثیرات عملکردی دارند که باید در نظر گرفته شوند. AsyncIO بار کمی اضافی ایجاد می‌کند اما نیازمند طراحی دقیق است تا از عملیات مسدود کننده جلوگیری شود. چندپردازشی بار بیشتری دارد به دلیل ایجاد فرآیند و ارتباط بین فرآیندها اما می‌تواند به طور کامل از سیستم‌های چند هسته‌ای استفاده کند.

نتیجه‌گیری

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

Share: