آموزش ماژول multiprocessing – کلاس Pool

Please login to bookmark Close

کلاس Pool چیست؟

کلاس Pool در ماژول multiprocessing یک «استخر» از پردازه‌های کارگر (worker) می‌سازد و کارها را بین آن‌ها پخش می‌کند. به‌جای اینکه خودتان دستی برای هر کار یک Process بسازید، start و join کنید، فقط یک فهرست از داده‌ها را به Pool می‌دهید و آن نتایج را به‌صورت موازی محاسبه و جمع‌آوری می‌کند. این کلاس مخصوصاً وقتی مفید است که می‌خواهید یک تابع را روی داده‌های زیادی به‌صورت موازی اجرا کنید.

مثال پایه: محاسبه‌ی مربع اعداد

یک Pool با تعداد مشخصی پردازه می‌سازیم و با map یک تابع را روی همه‌ی عناصر یک فهرست اجرا می‌کنیم:

from multiprocessing import Pool

def square(x):
    return x * x

if __name__ == "__main__":
    numbers = [1, 2, 3, 4, 5]

    # create a Pool with 3 worker processes
    with Pool(processes=3) as pool:
        results = pool.map(square, numbers)

    print(results)   # [1, 4, 9, 16, 25]

عدد ۳ می‌گوید سه پردازه‌ی موازی ساخته شود. دستور with کمک می‌کند که پس از پایان کار، Pool به‌طور خودکار بسته و منابعش آزاد شود (جلوگیری از نشت منابع). results فهرست نتایج را به همان ترتیب ورودی نگه می‌دارد.

متدهای مهم Pool

Pool چند روش برای ارسال کار دارد که هرکدام جای خودش را دارد:

  • map(func, iterable): تابع را روی هر عنصر اجرا می‌کند و نتایج را به ترتیب ورودی برمی‌گرداند (منتظر پایان همه می‌ماند).
  • starmap(func, iterable): مثل map اما برای توابعی که چند آرگومان می‌گیرند؛ هر عنصر یک tuple از آرگومان‌هاست.
  • apply(func, args): یک بار تابع را با آرگومان‌های داده‌شده اجرا می‌کند و منتظر نتیجه می‌ماند.
  • apply_async / map_async: نسخه‌های غیرمسدودکننده که فوراً یک شیء نتیجه برمی‌گردانند؛ بعداً با .get() نتیجه را می‌گیرید.
  • imap(func, iterable): نسخه‌ی «تنبل» (lazy) که نتایج را یکی‌یکی و به‌محض آماده‌شدن به‌صورت iterator می‌دهد؛ برای ورودی‌های خیلی بزرگ مناسب است.
from multiprocessing import Pool

def square(x):
    return x * x

def add(a, b):
    return a + b

if __name__ == "__main__":
    with Pool(processes=3) as pool:
        print(pool.map(square, [1, 2, 3, 4, 5]))          # [1, 4, 9, 16, 25]
        print(pool.starmap(add, [(1, 2), (3, 4), (5, 6)]))  # [3, 7, 11]

        result = pool.apply_async(square, (10,))           # non-blocking
        print(result.get())                                # 100

        for r in pool.imap(square, [1, 2, 3]):             # results as they arrive
            print(r)

اندازه‌ی مناسب Pool

اگر پارامتر processes را ندهید، Pool به‌صورت پیش‌فرض به تعداد هسته‌های پردازنده کارگر می‌سازد. برای کارهای CPU-bound معمولاً همین انتخاب خوبی است. می‌توانید تعداد هسته‌ها را با multiprocessing.cpu_count() بگیرید:

import multiprocessing
print("cores:", multiprocessing.cpu_count())

with multiprocessing.Pool() as pool:   # defaults to cpu_count() workers
    ...

مثال – کار سنگین موازی روی چند هسته

مهم‌ترین کاربرد Pool، موازی‌کردن کارهای CPU-bound (مثل محاسبات سنگین، پردازش تصویر یا رمزنگاری) روی چند هسته است. چون هر پردازه GIL مستقل خودش را دارد، این کارها واقعاً موازی اجرا می‌شوند. مقایسه‌ی اجرای ترتیبی و با Pool:

import time
from multiprocessing import Pool

def heavy(n):
    total = 0
    for i in range(n):
        total += i
    return total

if __name__ == "__main__":
    tasks = [10_000_000] * 4

    start = time.time()
    [heavy(t) for t in tasks]                 # sequential
    print("sequential: %.2fs" % (time.time() - start))

    start = time.time()
    with Pool() as pool:                      # parallel across cores
        pool.map(heavy, tasks)
    print("pool: %.2fs" % (time.time() - start))

روی یک ماشین چهارهسته‌ای، نسخه‌ی Pool چند برابر سریع‌تر از نسخه‌ی ترتیبی است. (به‌یاد داشته باشید که این سود فقط برای کارهای CPU-bound است؛ برای کارهای I/O-bound معمولاً threading یا asyncio انتخاب بهتری‌اند.)

جمع‌بندی

  • Pool یک استخر از پردازه‌های کارگر می‌سازد و کارها را بین آن‌ها پخش می‌کند؛ عالی برای اجرای یک تابع روی داده‌های زیاد به‌صورت موازی.
  • map (تک‌آرگومان)، starmap (چند آرگومان)، apply_async/map_async (غیرمسدودکننده) و imap (تنبل) روش‌های ارسال کارند.
  • با with کار کنید تا Pool خودکار بسته شود؛ اندازه‌ی پیش‌فرض برابر تعداد هسته‌هاست.
  • Pool برای کارهای CPU-bound سرعت را چند برابر می‌کند؛ برای I/O از threading/asyncio استفاده کنید.

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

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

78%
پیشرفت

سرفصل دوره

فهرست مطالب

سرفصل دوره

تمرین

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

پاسخ تمرین ها

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

اشتراک گذاری

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

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

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

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

تنظیمات

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