آموزش همگام سازی رشته ها در همزمانی پایتون

3 سال پیش
آموزش همگام سازی رشته ها در همزمانی پایتون

آموزش همگام سازی رشته ها در همزمانی پایتون

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

پیشنهاد ویژه : آموزش طراحی وب سایت با پایتون

مسائل مربوط به همگام سازی رشته در همزمانی پایتون

هنگام اجرای برنامه نویسی همزمانی یا استفاده از موارد ابتدایی همگام سازی ممکن است با مشکلاتی روبرو شویم. در این بخش، ما در مورد دو رشته عمده بحث خواهیم کرد. مسائل عبارتند از –
  • Deadlock
  • Race condition

Race condition

این یکی از مسائل مهم در برنامه نویسی همزمانی است. دسترسی همزمان به منابع مشترک می تواند منجر به شرایط نژادی شود. Race condition ممکن است به عنوان وقوع شرایطی تعریف شود که دو یا چند رشته بتوانند به داده های مشترک دسترسی داشته باشند و سپس سعی کنند مقدار آن را همزمان تغییر دهند. به همین دلیل، مقادیر متغیرها ممکن است قابل پیش بینی نباشند و بسته به زمان سوئیچ زمینه فرایندها متفاوت هستند.

مثال

برای درک مفهوم Race condition، این مثال را در نظر بگیرید –
مرحله ۱ – در این مرحله، ما باید ماژول رشته ای را وارد کنیم –
import threading

 

مرحله ۲ – اکنون یک متغیر جهانی را تعریف کنید، بگویید x، همراه با مقدار ۰ –
x = 0

 

مرحله ۳ – اکنون، ما باید تابع ()increment_global را تعریف کنیم، که این افزایش ۱ را در این تابع جهانی x انجام می دهد –
def increment_global():

   global x
   x += 1

 

مرحله ۴ – در این مرحله ، تابع ()taskofThread را تعریف می کنیم که برای تعداد مشخصی از بار، تابع ()increment_global را فراخوانی می کند. برای مثال ما ۵۰۰۰۰ برابر است –
def taskofThread():

   for _ in range(50000):
      increment_global()

 

مرحله ۵ – حال تابع ()main را که رشته های t1 و t2 در آن ایجاد می شوند ، تعریف کنید. هر دو با کمک تابع ()start شروع می شوند و منتظر می مانند تا با کمک ()join کار خود را تمام کنند.
def main():
   global x
   x = 0
   
   t1 = threading.Thread(target= taskofThread)
   t2 = threading.Thread(target= taskofThread)

   t1.start()
   t2.start()

   t1.join()
   t2.join()

 

مرحله ۶ – اکنون، باید محدوده مورد نظر را برای چند تکرار که می خواهیم تابع ()main فراخوانی کنیم، ارائه دهیم. در اینجا، ما ۵ بار با آن زا فراخوانی می کنیم.

if __name__ == "__main__":
   for i in range(5):
      main()
      print("x = {1} after Iteration {0}".format(i,x))

 

در خروجی نشان داده شده در زیر ، ما می توانیم تأثیر Race condition را ببینیم زیرا مقدار x پس از هر تکرار ۱۰۰۰۰۰ انتظار می رود. با این حال ، مقدار زیادی تغییر می کند. این به دلیل دسترسی همزمان رشته ها به متغیر جهانی x مشترک است.

خروجی

x = 100000 after Iteration 0
x = 54034 after Iteration 1
x = 80230 after Iteration 2
x = 93602 after Iteration 3
x = 93289 after Iteration 4

 

برخورد با Race condition با استفاده از Lock

همانطور که در برنامه فوق تأثیر Race condition را مشاهده کردیم، ما به یک ابزار همگام سازی نیاز داریم، که می تواند با شرایط مسابقه بین چندین رشته مقابله کند. در پایتون، ماژول <threading> کلاس Lock را برای مقابله با شرایط مسابقه فراهم می کند. بعلاوه، کلاس Lock روشهای مختلفی را ارائه می دهد که با کمک آنها می توانیم شرایط مسابقه را بین چندین رشته کنترل کنیم. روش ها در زیر شرح داده شده است –

متد ()acquire در همگام سازی رشته ها در همزمانی پایتون

این روش برای بدست آوردن، مسدود کردن قفل استفاده می شود. قفل بسته به مقدار true یا false زیر می تواند مسدود کننده یا غیر مسدود کننده باشد –
  • With value set to True – اگر متد ()acquire با True فراخوانی شود، که آرگومان پیش فرض است ، پس از آن تا زمانی که قفل باز نشود، اجرای رشته مسدود می شود.
  • With value set to False – اگر متد ()acquire با False فراخوانی شود ، که آرگومان پیش فرض نیست، پس از اجرای رشته تا زمانی که روی true تنظیم نشود ، مسدود نمی شود ، یعنی تا زمانی که قفل شود.

متد ()release

این متد برای آزاد سازی قفل استفاده می شود. در زیر چند کار مهم مربوط به این روش آورده شده است –
  • اگر lock قفل شود ، سپس متد ()release آن را باز می کند. وظیفه آن این است که اگر بیش از یک رشته مسدود شده و منتظر قفل شدن lock است، اجازه دهد دقیقاً یک رشته ادامه یابد.
  • اگر lock از قبل قفل نشده باشد، ThreadError را افزایش می دهد.
اکنون، می توانیم برنامه فوق را با کلاس lock و روش های آن دوباره بنویسیم تا از Race condition جلوگیری کنیم. ما باید متد () taskofThread را با آرگومان lock تعریف کنیم و سپس برای جلوگیری از شرایط مسابقه باید از متد های ()release و ()acquire برای مسدود کردن و عدم انسداد قفل ها استفاده کنیم.

مثال

در زیر مثالی از برنامه پایتون برای درک مفهوم lock برای مقابله با Race condition آورده شده است –
import threading

x = 0

def increment_global():

   global x
   x += 1

def taskofThread(lock):

   for _ in range(50000):
      lock.acquire()
      increment_global()
      lock.release()

def main():
   global x
   x = 0

   lock = threading.Lock()
   t1 = threading.Thread(target = taskofThread, args = (lock,))
   t2 = threading.Thread(target = taskofThread, args = (lock,))

   t1.start()
   t2.start()

   t1.join()
   t2.join()

if __name__ == "__main__":
   for i in range(5):
      main()
      print("x = {1} after Iteration {0}".format(i,x))

 

خروجی زیر نشان می دهد که اثر Race condition د نادیده گرفته می شود. به عنوان مقدار x ، پس از هر تکرار ، اکنون ۱۰۰۰۰۰ است ، که طبق انتظار این برنامه است.

خروجی

x = 100000 after Iteration 0
x = 100000 after Iteration 1
x = 100000 after Iteration 2
x = 100000 after Iteration 3
x = 100000 after Iteration 4

Deadlocks در همگام سازی رشته ها در همزمانی پایتون

Deadlocks یا بن بست مسئله ای دردسر ساز است که می توان هنگام طراحی سیستم های همزمانس با آن روبرو شد. ما می توانیم این مسئله را با کمک مسئله Dining Philosophers به شرح زیر نشان دهیم –
Edsger Dijkstra در اصل مسئله Dining Philosophers را معرفی کرد ، یکی از تصاویر مشهور یکی از بزرگترین مشکل سیستم همزمان بنام Deadlock
در این مشکل، پنج فیلسوف مشهور سر یک میز گرد نشسته اند و از کاسه های خود مقداری غذا می خورند. پنج چنگال وجود دارد که می تواند توسط پنج فیلسوف برای خوردن غذای خود استفاده شود. با این حال، فلاسفه تصمیم می گیرند تا همزمان از دو چنگال برای خوردن غذای خود استفاده کنند.
اکنون، دو شرط اصلی برای فلاسفه وجود دارد. اولاً، هر یک از فلاسفه می توانند در حالت غذا خوردن یا در حالت تفکر باشند و ثانیا، آنها ابتدا باید هر دو چنگال را بدست آورند، یعنی چپ و راست. این مسئله وقتی به وجود می آید که هر یک از پنج فیلسوف موفق شوند چنگال چپ را همزمان بردارند. اکنون همه آنها منتظر هستند تا چنگال راست آزاد شود اما هرگز غذای خود را نخورند و چنگال راست هرگز در دسترس نخواهد بود. از این رو، در میز شام وضعیت Deadlock ایجاد خواهد شد.

بن بست در سیستم همزمانی

حال اگر ببینیم، همین مسئله می تواند در سیستم های همزمانی ما نیز بوجود آید. چنگالهای مثال بالا منابع سیستم خواهد بود و هر فیلسوف می تواند فرآیندی را که برای بدست آوردن منابع در حال رقابت است ، نمایندگی کند.

راه حل با برنامه پایتون

راه حل این مسئله را می توان با تقسیم فیلسوفان به دو نوع – فیلسوفان حریص و فیلسوفان سخاوتمند. عمدتا یک فیلسوف حریص سعی می کند چنگال چپ را برداشته و منتظر بماند. او سپس منتظر خواهد ماند تا چنگال راست آزاد شود، آن را برداشته، غذا بخورد و سپس آن را زمین بگذارد. از طرف دیگر، یک فیلسوف سخاوتمندانه سعی می کند چنگال چپ را برداشته و اگر در آنجا نباشد، منتظر می ماند و بعد از مدتی دوباره امتحان می کند. اگر چنگال چپ را بدست آورند، سعی می کنند چمدان درست را بدست آورند. اگر آنها نیز چنگال راست بدست آورند ، هر دو چنگال را می خورند و آزاد می کنند. با این حال، اگر آنها چنگال راست را بدست نیاورند ، چنگال چپ را آزاد می کنند.

مثال

برنامه پایتون زیر به ما کمک می کند تا راه حلی برای مسئله Dining Philosophers پیدا کنیم –
import threading
import random
import time

class DiningPhilosopher(threading.Thread):

   running = True

   def __init__(self, xname, Leftfork, Rightfork):
   threading.Thread.__init__(self)
   self.name = xname
   self.Leftfork = Leftfork
   self.Rightfork = Rightfork

   def run(self):
   while(self.running):
      time.sleep( random.uniform(3,13))
      print ('%s is hungry.' % self.name)
      self.dine()

   def dine(self):
   fork1, fork2 = self.Leftfork, self.Rightfork

   while self.running:
      fork1.acquire(True)
      locked = fork2.acquire(False)
      if locked: break
      fork1.release()
      print ('%s swaps forks' % self.name)
      fork1, fork2 = fork2, fork1
   else:
      return

   self.dining()
   fork2.release()
   fork1.release()

   def dining(self):
   print ('%s starts eating '% self.name)
   time.sleep(random.uniform(1,10))
   print ('%s finishes eating and now thinking.' % self.name)

def Dining_Philosophers():
   forks = [threading.Lock() for n in range(5)]
   philosopherNames = ('1st','2nd','3rd','4th', '5th')

   philosophers= [DiningPhilosopher(philosopherNames[i], forks[i%5], forks[(i+1)%5]) \
      for i in range(5)]

   random.seed()
   DiningPhilosopher.running = True
   for p in philosophers: p.start()
   time.sleep(30)
   DiningPhilosopher.running = False
   print (" It is finishing.")

Dining_Philosophers()
برنامه فوق از مفهوم فلاسفه حریص و سخاوتمندانه استفاده می کند. این برنامه همچنین از متد های ()release و ()acquire کلاس Lock از ماژول <threading> استفاده کرده است. ما می توانیم راه حل را در خروجی زیر مشاهده کنیم –

خروجی

۴th is hungry.
۴th starts eating
۱st is hungry.
۱st starts eating
۲nd is hungry.
۵th is hungry.
۳rd is hungry.
۱st finishes eating and now thinking.3rd swaps forks
۲nd starts eating
۴th finishes eating and now thinking.
۳rd swaps forks5th starts eating
۵th finishes eating and now thinking.
۴th is hungry.
۴th starts eating
۲nd finishes eating and now thinking.
۳rd swaps forks
۱st is hungry.
۱st starts eating
۴th finishes eating and now thinking.
۳rd starts eating
۵th is hungry.
۵th swaps forks
۱st finishes eating and now thinking.
۵th starts eating
۲nd is hungry.
۲nd swaps forks
۴th is hungry.
۵th finishes eating and now thinking.
۳rd finishes eating and now thinking.
۲nd starts eating 4th starts eating
It is finishing.

 

منبع.

0
برچسب ها :
نویسنده مطلب erfan molaei

دیدگاه شما

بدون دیدگاه