Python 同步線程

python 同步線程

線程同步可以被定義為一種方法,借助于該方法,我們可以確保兩個(gè)或更多并發(fā)線程不同時(shí)訪問稱為臨界區(qū)的程序段。另一方面,正如我們所知,臨界區(qū)是訪問共享資源的程序的一部分。因此,我們可以說同步是通過同時(shí)訪問資源來確保兩個(gè)或多個(gè)線程不相互連接的過程。下圖顯示了四個(gè)線程同時(shí)嘗試訪問程序的關(guān)鍵部分。

為了更清楚,假設(shè)有兩個(gè)或更多線程試圖同時(shí)在列表中添加對(duì)象。此行為無法導(dǎo)致成功結(jié)束,因?yàn)樗鼘G棄一個(gè)或所有對(duì)象,否則將完全破壞列表的狀態(tài)。這里同步的作用是一次只有一個(gè)線程可以訪問列表。

 

線程同步中的問題

在實(shí)現(xiàn)并發(fā)編程或應(yīng)用同步原語時(shí),我們可能會(huì)遇到問題。在本節(jié)中,我們將討論兩個(gè)主要問題。問題是

  • deadlock
  • race condition

比賽條件

這是并發(fā)編程中的主要問題之一。對(duì)共享資源的并發(fā)訪問可能導(dǎo)致競爭條件。競爭條件可以定義為當(dāng)兩個(gè)或多個(gè)線程可以訪問共享數(shù)據(jù)然后嘗試同時(shí)更改其值時(shí)發(fā)生的條件。因此,變量的值可能是不可預(yù)測(cè)的,并且取決于過程的上下文切換的定時(shí)而變化。

考慮這個(gè)例子來理解競爭條件的概念 -

第1步 - 在這一步中,我們需要導(dǎo)入線程模塊 -

import threading

第2步 - 現(xiàn)在,定義一個(gè)全局變量,比如x,以及它的值為0 -

x = 0

第3步 - 現(xiàn)在,我們需要定義 increment_global() 函數(shù),它將在此全局函數(shù)x中增加1 -

def increment_global():

   global x
   x += 1

步驟4 - 在這一步中,我們將定義 taskofthread() 函數(shù),該函數(shù)將調(diào)用increment_global()函數(shù)指定的次數(shù); 對(duì)于我們的例子它是50000次 -

def taskofthread():

   for _ in range(50000):
      increment_global()

步驟5 - 現(xiàn)在,定義main()函數(shù),其中創(chuàng)建了線程t1和t2。 兩者都將在start()函數(shù)的幫助下啟動(dòng),并等到他們?cè)趈oin()函數(shù)的幫助下完成工作。

def main():
   global x
   x = 0

   t1 = threading.thread(target= taskofthread)
   t2 = threading.thread(target= taskofthread)

   t1.start()
   t2.start()

   t1.join()
   t2.join()

第6步 - 現(xiàn)在,我們需要給出我們想要調(diào)用main()函數(shù)的迭代次數(shù)的范圍。 在這里,我們稱它為5次。

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

在下面顯示的輸出中,我們可以看到競爭條件的影響,因?yàn)槊看蔚髕的值預(yù)計(jì)為100000.但是,值有很多變化。這是由于線程并發(fā)訪問共享全局變量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

 

使用鎖處理競爭條件

正如我們?cè)谏厦娴某绦蛑锌吹礁偁帡l件的影響,我們需要一個(gè)同步工具,它可以處理多個(gè)線程之間的競爭條件。在python中, 模塊提供了lock類來處理競爭條件。此外, lock 類提供了不同的方法,我們可以幫助處理多個(gè)線程之間的競爭條件。方法如下所述

acquire()方法

該方法用于獲取,即阻止鎖定。鎖定可以是阻塞或非阻塞,具體取決于以下真值或假值

  • 將值設(shè)置為true - 如果使用true(默認(rèn)參數(shù))調(diào)用acquire()方法,則會(huì)阻止線程執(zhí)行,直到解鎖為止。

  • 將值設(shè)置為false - 如果使用false調(diào)用acquire()方法(這不是默認(rèn)參數(shù)),則在將其設(shè)置為true(即直到鎖定)之前,不會(huì)阻止線程執(zhí)行。

release()方法

此方法用于釋放鎖定。以下是與此方法相關(guān)的一些重要任務(wù)

  • 如果鎖被鎖定,那么 release() 方法將解鎖它。它的工作是,如果多個(gè)線程被阻塞并等待鎖解鎖,則只允許一個(gè)線程繼續(xù)運(yùn)行。

  • 如果鎖已經(jīng)解鎖,它將引發(fā) threaderror 。

現(xiàn)在,我們可以使用lock類及其方法重寫上述程序,以避免競爭條件。我們需要使用lock參數(shù)定義taskofthread()方法,然后需要使用acquire()和release()方法來阻止和非阻塞鎖以避免競爭條件。

以下是了解用于處理競爭條件的鎖概念的python程序示例 -

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))

以下輸出表明忽略了競爭條件的影響; 因?yàn)閤的值,在每次迭代之后,現(xiàn)在是100000,這符合該程序的期望。

輸出

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

 

死鎖 - 餐飲哲學(xué)家的問題

死鎖是設(shè)計(jì)并發(fā)系統(tǒng)時(shí)可能遇到的麻煩問題。我們可以借助餐飲哲學(xué)家的問題來說明這個(gè)問題如下 -

edsger dijkstra最初介紹了餐飲哲學(xué)家的問題,其中一個(gè)著名的插圖是并發(fā)系統(tǒng)中最大的問題之一,稱為死鎖。

在這個(gè)問題上,有五位著名的哲學(xué)家坐在圓桌旁,從他們的碗里吃一些食物。五個(gè)哲學(xué)家可以使用五把叉來吃他們的食物。然而,哲學(xué)家們決定同時(shí)使用兩把叉來吃他們的食物。

現(xiàn)在,哲學(xué)家有兩個(gè)主要條件。首先,每個(gè)哲學(xué)家可以處于進(jìn)食狀態(tài)或處于思維狀態(tài),其次,他們必須首先獲得兩個(gè)分叉,即左右分支。當(dāng)五位哲學(xué)家中的每一位都設(shè)法同時(shí)選擇左叉時(shí),問題就出現(xiàn)了。現(xiàn)在他們都在等待正確的叉子自由,但他們永遠(yuǎn)不會(huì)放棄他們的叉子,直到他們吃了他們的食物并且正確的叉子永遠(yuǎn)不可用。因此,餐桌上會(huì)出現(xiàn)死鎖狀態(tài)。

并發(fā)系統(tǒng)中的死鎖

現(xiàn)在,如果我們看到,我們的并發(fā)系統(tǒng)也會(huì)出現(xiàn)同樣的問題。上例中的分支將是系統(tǒng)資源,每個(gè)哲學(xué)家都可以代表競爭獲取資源的過程。

使用python程序解決方案

通過將哲學(xué)家分為兩類 - 貪婪的哲學(xué)家 和 慷慨的哲學(xué)家, 可以找到這個(gè)問題的解決方案。主要是一個(gè)貪婪的哲學(xué)家將嘗試拿起左叉并等到它在那里。然后他會(huì)等待正確的叉子在那里,撿起來吃,然后把它放下。另一方面,一個(gè)慷慨的哲學(xué)家會(huì)試圖拿起左叉,如果不存在,他會(huì)等一段時(shí)間再試一次。如果他們得到左叉,那么他們會(huì)嘗試找到合適的叉子。如果他們也會(huì)獲得正確的叉子,那么他們就會(huì)吃掉并釋放兩個(gè)叉子。但是,如果他們不能獲得正確的分叉,那么他們將釋放左分叉。

以下python程序?qū)椭覀冋业讲惋嬚軐W(xué)家問題的解決方案

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()

上述計(jì)劃使用了貪婪和慷慨的哲學(xué)家的概念。該程序還使用了 < threading>模塊的 lock 類的 acquire() 和 release() 方法。我們可以在以下輸出中看到解決方案

輸出

4th is hungry.
4th starts eating
1st is hungry.
1st starts eating
2nd is hungry.
5th is hungry.
3rd is hungry.
1st finishes eating and now thinking.3rd swaps forks
2nd starts eating
4th finishes eating and now thinking.
3rd swaps forks5th starts eating
5th finishes eating and now thinking.
4th is hungry.
4th starts eating
2nd finishes eating and now thinking.
3rd swaps forks
1st is hungry.
1st starts eating
4th finishes eating and now thinking.
3rd starts eating
5th is hungry.
5th swaps forks
1st finishes eating and now thinking.
5th starts eating
2nd is hungry.
2nd swaps forks
4th is hungry.
5th finishes eating and now thinking.
3rd finishes eating and now thinking.
2nd starts eating 4th starts eating
it is finishing.

下一節(jié):python 線程通信

相關(guān)文章
亚洲国产精品第一区二区,久久免费视频77,99V久久综合狠狠综合久久,国产免费久久九九免费视频