ماژول threading – کلاس Lock و RLock

Please login to bookmark Close

پیش‌نیاز

قبل از مطالعه این بخش، باید با مفهوم قفل کردن منابع مشترک و چرایی آن آشنا باشید.

قفل گذاری در threading.Thread

قفل‌گذاری در threading.Thread کاملا مشابه قفل کردن منابع مشترک در ماژول _thread در ۳ مرحله انجام می‌شود.

  • ایجاد قفل
  • بستن قفل
  • باز کردن قفل

برای دیدن نحوه انجام سه مرحله فوق، به تکه کد زیر توجه کنید.

from threading import Thread, Lock

# تعریف قفل
lock = Lock()

def add():
    global number
    for i in range(100000):
        # قفل کردن قبل از دسترسی به متغیر مشترک
        lock.acquire()
        number += 1
        # آزاد کردن قفل بعد از دسترسی
        lock.release()

def subtract():
    global number
    for i in range(100000):
        # قفل کردن قبل از دسترسی به متغیر مشترک
        lock.acquire()
        number -= 1
        # آزاد کردن قفل بعد از دسترسی
        lock.release()

number = 0

# ایجاد و شروع رشته‌ها
thread1 = Thread(target=add)
thread2 = Thread(target=subtract)

thread1.start()
thread2.start()

# منتظر ماندن برای اتمام رشته‌ها
thread1.join()
thread2.join()

# چاپ مقدار نهایی number
print(number)
  • خط ۴: ایجاد قفل – ابتدا با ساخت یک instance از کلاس Lock، یک قفل می‌سازیم.
  • خط ۱۰ و ۱۹: بستن قفل – سپس با فراخوانی متد acquire قبل از استفاده از منبع مشترک، آن را قفل می‌کنیم. اگر قبلا، ترد دیگری آن را قفل کرده باشد، ترد در این قسمت متوقف شده و آنقدر منتظر می‌ماند تا ترد دیگر قفل را باز کند.
  • خط ۱۳ و ۲۲: باز کردن قفل – در نهایت، زمانی که کارمان با منبع مشترک تمام شد، با فراخوانی متد release، آن منبع مشترک را آزاد می‌کنیم.

هنگام استفاده از کلاس Lock برای قفل کردن ممکن است خطای Dead Lock رخ دهد. برای کاهش احتمال بروز خطا، اقدامات زیر را انجام دهید.

در ادامه به شرح هر یک می‌پردازیم.

استفاده از Rlock برای قفل گذاری

Rlock (Recursive Lock) بیشتر مناسب سناریو هایی است که ترد ها با الگویی بازگشتی توسعه داده شده باشند. با استفاده از Rlock، می‌توان بارها و بارها بدون نگرانی از اینکه خطای Dead Lock رخ دهد، متد acquire را فراخوانی کرد.

مثال – اتاق سرور بانک مرکزی

فرض کنید می‌خواهید وارد اتاق سرور بانک مرکزی شوید. سرورها در اتاق A قرار دارند. اتاق A در اتاق B و اتاق B نیز در اتاق C قرار دارد. برای وارد شدن به هر اتاق باید درب اتاق قبلی را پشت سر خود قفل کنید. در این زمان که شما داخل هستید شخص دیگری نمی‌تواند درب را باز کرده و وارد شود. همچنین وقتی می‌خواهید از اتاق‌ها خارج شوید باید قفل ها را یکی پس از دیگری باز کنید تا نهایتا از اتاق‌ها خارج شوید.

اگر برای قفل گذاری از Lock استفاده کرده باشید، هنگاهی که یک ترد به acquire برسد و قبلا عملیات قفل گذاری انجام شده باشد، برنامه متوقف شده و دچار خطای Dead Lock می‌شود اما اگر از RLock استفاده شده باشد، برنامه بدون توقف ادامه یافته و صرفا یک قفل روی قفل قبلی گذاشته می‌شود.

می‌توان اینطور در نظر گرفت که هنگام استفاده از Rlock تعداد دفعات فراخوانی acquire در یک شمارنده ذخیره می‌شود و در نهایت به همان تعداد باید متد release را فراخوانی کنیم.

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

import threading

# ایجاد یک قفل بازیابی
rlock = threading.RLock()

def rfunc(n):
    if n <= 0:
        return
    rlock.acquire()
    try:
        print(f"Thread {threading.current_thread().name} acquired lock with n = {n}")
        rfunc(n - 1)
        print(f"Thread {threading.current_thread().name} released lock with n = {n}")
    finally:
        rlock.release()

# ایجاد و شروع نخ‌ها
threads = []
for i in range(3):
    t = threading.Thread(target=rfunc, args=(3,), name=f'Thread-{i}')
    threads.append(t)
    t.start()

# منتظر ماندن برای تمام نخ‌ها
for t in threads:
    t.join()

print("All threads have finished execution.")
  • خط ۴: یک نمونه از کلاس Rlock ساخته‌ایم.
  • خط ۶ تا ۱۵: یک تابع به نام rfunc ساخته‌ایم که در خودش عملیات قفل‌گذاری را انجام داده و همچنین خودش را نیز به صورت بازگشتی در خط ۱۲ فراخوانی می‌کند.

استفاده از with برای قفل گذاری

به جای استفاده از acquire و release می‌توان از دستور with برای قفل کردن استفاده کرد. این کار علاوه بر افزایش خوانایی کد، موجب کاهش احتمال بروز خطای Dead Lock می‌شود.

from threading import Thread, Lock

# تعریف قفل
lock = Lock()

def add():
    global number
    for i in range(100000):
        # قفل کردن قبل از دسترسی به متغیر مشترک با استفاده از with
        with lock:
            number += 1

def subtract():
    global number
    for i in range(100000):
        # قفل کردن قبل از دسترسی به متغیر مشترک با استفاده از with
        with lock:
            number -= 1

number = 0

# ایجاد و شروع رشته‌ها
thread1 = Thread(target=add)
thread2 = Thread(target=subtract)

thread1.start()
thread2.start()

# منتظر ماندن برای اتمام رشته‌ها
thread1.join()
thread2.join()

# چاپ مقدار نهایی number
print(number)

خلاصه

  • هنگامی که منابع مشترک بین ترد ها وجود دارد، برای جلوگیری از رخ دادن مشکلات الگوریتمی ناشی از Race Condition، ترد ها را قفل می‌کنیم.
  • برای افزایش خوانایی کد و کاهش خطای Dead Lock استفاده از with به جای acquire و release پیشنهاد می‌شود.
Please login to bookmark Close
نظرات

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

فهرست مطالب

سرفصل دوره

تمرین

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

پاسخ تمرین ها

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

اشتراک گذاری

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

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

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

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

تنظیمات

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