آموزش ماژول multiprocessing – ایجاد Process

Please login to bookmark Close

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

در multiprocessing به هر پردازش منابع جداگانه‌ای اختصاص می‌یابد. مثلا اگر دو process همزمان به یک لیست با مقدار [1, 2, 3] نیاز داشته باشند، این لیست یک بار برای پردازش اول و یک بار هم برای پردازش دوم در RAM ساخته می‌شود. البته با ترفندهایی می‌توان منابع را بین process ها به اشتراک گذاشت که این امر بهینگی را کاهش می‌دهد.

بین همه تکنیک های Concurrent Programming، تنها multiprocessing است که با اجرای برنامه روی چند پردازشگر واقعا برنامه‌ها را به صورت parallel اجرا می‌کند. پس بهتر است تسک‌های CPU Bound را با استفاده از multiprocessing انجام دهیم.

با توجه به اینکه هر process در multiprocessing منابع اختصاصی خود را می‌خواهد، استفاده بی‌رویه از multiprocessing می‌تواند ما را با بحران کمبود منابع سخت‌افزاری مواجه کند.

نحوه اجرای یک برنامه به صورت multiprocessing

برای ایجاد process با استفاده از ماژول multiprocessing دو راه داریم:

  • روش اول – ساختن instance از کلاس Process
  • روش دوم – ارث‌بری از کلاس Process

روش اول – ساختن instance از کلاس Process

به تکه کد زیر توجه کنید. این کد شامل دو تابع است که اجرای هر کدام ۲ ثانیه طول می‌کشد. به عبارت دیگر اجرای کل برنامه به ۴ ثانیه زمان نیاز دارد.

from time import sleep

def task1():
    print('Process 1 started')
    sleep(2)
    print('Process 1 completed')

def task2():
    print('Process 2 started')
    sleep(2)
    print('Process 2 completed')

task1()
task2()

اکنون با کمی تغییر در کد بالا، این دو تابع را به صورت موازی اجرا کنیم که در این صورت زمان اجرای برنامه به ۲ ثانیه کاهش می‌یابد.

from time import sleep
from multiprocessing import Process

def task1():
    print('Process 1 started')
    sleep(2)
    print('Process 1 completed')

def task2():
    print('Process 2 started')
    sleep(2)
    print('Process 2 completed')

if __name__ == "__main__":
    Process(target=task1).start()
    Process(target=task2).start()

توضیح کد بالا:

  • خط ۴ تا ۷: یک تابع تعریف شده که اجرای آن ۲ ثانیه طول می‌کشد.
  • خط ۹ تا ۱۲: یک تابع تعریف شده که اجرای آن ۲ ثانیه طول می‌کشد.
  • خط ۱۵: تابع task1 را به عنوان یک برنامه مستقل اجرا می‌کند.
  • خط ۱۶: تابع task2 را به عنوان یک برنامه مستقل اجرا می‌کند.

در تکه کد بالا هر تابع به صورت کاملا مستقل از دیگری اجرا می‌شود. این دو تابع هیچ درکی از یک دیگر ندارند. اجرای این دو تابع در process های جداگانه مانند این است که این دو تابع هر کدام در یک فایل جداگانه نوشته و اجرا شده باشند.

ارسال ورودی به process

در صورتی که process به ورودی نیاز داشته باشد، می‌توان ورودی ها را تحت عنوان args به آن فرستاد. به تکه کد زیر توجه کنید.

from multiprocessing import Process

def task(name):
    print(f"process {name} is running")

Process(target=task, args=('p1', )).start()
#                    ^^^^^^^^^^^^^

توضیح کد بالا:

  • خط ۳ تا ۴: یک تابع تعریف شده که یک ورودی می‌گیرد. این ورودی قرار است آبجکت یک process باشد.
  • خط ۶: تابع task به عنوان یک process اجرا شده است. در این قسمت ورودی های مورد نیاز task تحت عنوان args به آن ارسال شده است.

روش دوم – ارث‌بری از کلاس Process

به تکه کد زیر توجه کنید. این کد شامل دو تابع است که اجرای هر کدام ۲ ثانیه طول می‌کشد. به عبارت دیگر اجرای کل برنامه به ۴ ثانیه زمان نیاز دارد.

from time import sleep

def task1():
    print('Process 1 started')
    sleep(2)
    print('Process 1 completed')

def task2():
    print('Process 2 started')
    sleep(2)
    print('Process 2 completed')

task1()
task2()

اکنون با ارث‌بری از کلاس Process کلاس MyProcess را می‌سازیم تا با استفاده از آن این دو تابع را به صورت موازی اجرا کنیم که در این صورت زمان اجرای برنامه به ۲ ثانیه کاهش می‌یابد.

from time import sleep
from multiprocessing import Process

class MyProcess(Process):
    pass

def task1():
    print('Process 1 started')
    sleep(2)
    print('Process 1 completed')

def task2():
    print('Process 2 started')
    sleep(2)
    print('Process 2 completed')

if __name__ == "__main__":
    MyProcess(target=task1).start()
    MyProcess(target=task2).start()

با ساختن کلاس MyProcess و ارث‌بری آن از Process می‌توان به امکانات زیادی که برنامه‌نویسی شی‌گرا در اختیار ما قرار می‌دهد دسترسی پیدا کنیم. این امکانات در بخش بعدی به تفصیل توضیح داده شده است. به همین دلیل از شرح آن‌ها در این قسمت صرف نظر می‌کنیم.

درک مستقل بودن پردازش ها

به تکه کد زیر توجه کنید. این تکه کد به صورت sequential توسعه یافته است.

lst = []

def task1():
    lst.append(1)
    print('lst value from task1:', lst)

def task2():
    lst.append(2)
    print('lst value from task2:', lst)

task1()
task2()
print('lst final value:', lst)

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

lst value from task1: [1]
lst value from task2: [1, 2]
lst final value: [1, 2]

همانطور که انتظار می‌رود، هر یک از توابع مقدار جدیدی را به lst اضافه می‌کنند و در نهایت مقدار lst با مقدار اولیه اش متغیر است.

اکنون در ادامه همین کد را به روش multiprocessing بازنویسی می‌کنیم.

from multiprocessing import Process

lst = []

def task1():
    lst.append(1)
    print('lst value from task1:', lst)

def task2():
    lst.append(2)
    print('lst value from task2:', lst)

Process(target=task1).start()
Process(target=task2).start()
print('lst final value:', lst)

توضیح کد بالا:

  • خط ۳: یک لیست تعریف شده تا مقدارش را بعدا در task1 و task2 تغییر دهیم.
  • خط ۵ تا ۷: یک تابع تعریف شده که عدد 1 را به lst اضافه می‌کند.
  • خط ۹ تا ۱۱: یک تابع تعریف شده که عدد 2 را به lst اضافه می‌کند.
  • خط ۱۳: تابع task1 را به عنوان یک برنامه مستقل اجرا می‌کند.
  • خط ۱۴: تابع task2 را به عنوان یک برنامه مستقل اجرا می‌کند.
  • خط ۱۵: مقدار نهایی lst را چاپ می‌کند.

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

[tabby title=”خروجی اول”]


lst final value: []
lst value from task1: [1]
lst value from task2: [2]

[tabby title=”خروجی دوم”]


lst value from task1: [1]
lst final value: []
lst value from task2: [2]

[tabbyending]

با توجه به این خروجی دو نکته مهم قابل برداشت است.

  • نکته اول: مقدار نهایی lst با مقدار آن در هر یک از توابع task1 و task2 متفاوت است. این نشان می‌دهد که lst در هر یک از این توابع عملکرد مستقلی دارد.
  • نکته دوم: نمی‌توان کاملا با قطعیت گفت که process ها با چه ترتیبی اجرا می‌شوند. هر بار که این برنامه اجرا شود ممکن است ترتیب print ها متفاوت باشد. این مورد برای thread ها هم وجود داشت. این سیستم‌عامل است که تصمیم می‌گیرد کدام thread یا process ابتدا اجرا شود. ما فقط با اجرای متد start به سیستم‌عامل اطلاع می‌دهیم که چه thread یا process ای آماده اجرا است.

اهمیت استفاده از __name__ == "__main__"

process ها در سیستم‌عامل‌های یونیکسی مانند لینوکس و macOS با استفاده از تابع fork ساخته می‌شوند. به همین دلیل هر process می‌داند که باید از همان نقطه‌ای که process والد متوقف شده بود، ادامه دهد.

این در صورتی است که process ها در ویندوز با استفاده از تابع spawn ساخته می‌شوند. تابع spawn یک process جدید ایجاد می‌کند که کل اسکریپت پایتون را از ابتدا اجرا می‌کند. در این حالت اگر از این شرط استفاده نشود process جدید نیز دوباره کد ایجاد process ها را اجرا می‌کند که منجر به ایجاد بی‌نهایت process و مصرف بیش از حد منابع سیستم می‌شود.

به تکه کد زیر توجه کنید. در این کد از این شرط استفاده نشده است.

from time import sleep
from multiprocessing import Process

def task1():
    print('Process 1 started')
    sleep(2)
    print('Process 1 completed')

def task2():
    print('Process 2 started')
    sleep(2)
    print('Process 2 completed')

Process(target=task1).start()
Process(target=task2).start()

در صورت اجرای این کد در ویندوز با خطای زیر مواجه خواهید شد.

Please login to bookmark Close
نظرات

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

فهرست مطالب

سرفصل دوره

تمرین

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

پاسخ تمرین ها

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

اشتراک گذاری

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

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

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

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

تنظیمات

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