Python——生產者消費者問題、死鎖、同步和異步

多線程產生的問題

​ 併發編程由於需要很多線程、進程之間的協作,所以很容易出現問題,下面主要介紹生產者與消費者問題、死鎖問題、同步和異步問題

生產者與消費者問題

​ 當我們進行一個任務,需要兩個線程不斷地獲取和操作數據時,可能會產生一個問題,如果數據獲取很快,而操作很慢,那麼獲取數據的線程就必須等待操作數據的線程處理完畢,反之如果數據獲取的很慢而操作的很快,那麼操作數據的線程就必須等待獲取數據的線程,這種問題可以看作是一種生產消費能力的不平衡,稱之爲生產者與消費者問題

解決方法

​ 解決這種生產消費能力不平衡的方法就是,在在生產者和消費者中間設立一個緩衝機制,生產者將生產的數據放入緩衝池中,消費者從緩衝池中取出數據進行處理,當緩衝池中的數據量小於一定的值時,生產者就會向緩衝池中添加數據,而當緩衝池中數據大於一個值時,消費者就會從緩衝池中取出數據,使生產者和消費者達到一種動態的平衡

​ 實現代碼:

import threading
import time
from queue import Queue


class Producer(threading.Thread):
    def run(self):
        global Queue
        count = 0
        while True:
            if queue.qsize() < 1000:
                for i in range(100):
                    count  = 1
                    msg = 'Pro '   str(count)
                    queue.put(msg)
            time.sleep(0.5)


class Consumer(threading.Thread):
    def run(self):
        global queue
        while True:
            if queue.qsize() > 100:
                for i in range(3):
                    msg = self.name   'Spend '   queue.get()
                    print(msg)
            time.sleep(1)


if __name__ == '__main__':
    queue = Queue()

    for i in range(500):
        queue.put('Start Pro '   str(i))
    for i in range(2):
        p = Producer()
        p.start()
    for i in range(5):
        c = Consumer()
        c.start()

​ 運行結果(截取):

Thread-7Spend Start Pro 494
Thread-3Spend Start Pro 495
Thread-5Spend Start Pro 496
Thread-5Spend Start Pro 498
Thread-5Spend Start Pro 499
Thread-3Spend Start Pro 497
Thread-3Spend Pro 1
Thread-4Spend Pro 2
Thread-4Spend Pro 3
Thread-4Spend Pro 4
Thread-6Spend Pro 5
Thread-6Spend Pro 6

死鎖問題

​ 在多線程中,線程可以通過互斥鎖來保證對同一資源的唯一佔有,但當程序變得複雜後,可能會出現線程 A 對資源 A 上了鎖,而線程 A 後邊需要用到資源 B,使用完畢後纔會對資源 A解鎖,而線程 B 對資源 B 上了鎖,它後邊選要用到資源 A,用過後纔會給 B 解鎖,如果線程 A 和線程 B 同時運行,就可能會造成一種情況:線程 A 在等待線程 B 解鎖,線程 B 也在等待線程 A 解鎖,這就是死鎖問題

死鎖模擬

import threading
import time


class MyThraed1(threading.Thread):
    def run(self):
        if mutexA.acquire():
            print(self.name   '----do 1----up----')
            time.sleep(1)
            if mutexB.acquire():
                print(self.name   '----do 1----down----')
                mutexB.release()
            mutexA.release()


class MyThread2(threading.Thread):
    def run(self):
        if mutexB.acquire():
            print(self.name   '----d0 2----up----')
            time.sleep(1)
            if mutexA.acquire():
                print('----do 2----down----')
                mutexA.release()
            mutexB.release()


mutexA = threading.Lock()
mutexB = threading.Lock()

if __name__ == '__main__':
    t1 = MyThraed1()
    t2 = MyThread2()

    t1.start()
    t2.start()

​ 以上代碼運行後,毫無疑問會產生死鎖問題,結果如下

Thread-1----do 1----up----
Thread-2----d0 2----up----

​ 程序由於三個線程都在等待對方釋放資源而卡住了

解決方法

​ 死鎖問題應該儘量在設計程序時避免,或添加等待超時時間,從而檢測程序是否產生了死鎖,另一種就是通過銀行家算法也可以避免死鎖問題

銀行家算法

​ 銀行家算法的思想就是,假設銀行有 10 元,這個時候有三個人提出貸款,A 要貸款 9 元,B 要貸款 3 元,C 要貸款 8 元,這時,銀行肯定不夠將所有人都滿足,銀行家算法就誕生了

​ 這時銀行爲了留住所有客戶並且保證自己的錢不會不足,便分批貸款給客戶,先借給 A 2 元、B 2 元、C 4 元,銀行還剩 2 元,此時 B 直需要再借 1 元就滿足了他自己的需求,銀行便借給他 1 元,自己剩 1 元,當 B 用完,將 3 元還給銀行後,銀行再將這 4 元借給 C,C 也就滿足了,等 C 還款後,再將 8 元中的 7 元借給 A,這樣便動態的滿足了三個客戶的需求

​ 銀行家算法在程序中實際上也是模擬了銀行貸款的過程,操作系統會動態的向各個線程分配資源,在分配前,系統會判斷分配後會不會導致系統進入不安全狀態,不會就分配資源給線程,會則令線程等待

同步和異步

​ 同步和異步是併發編程下的兩種重要的狀態

同步

​ 同步是指當程序 A 調用程序 B 時,程序 A 停下不動,等待程序 B 完成後再繼續運行

​ 舉個例子就是,假設 A 喊 B 出去吃飯,B 說等我寫完代碼再去,A 就一直在原地等着 B,這就是同步

​ 歸根結底,同步實現的就是一種順序的運行

使用互斥鎖實現線程同步

​ 代碼:

from threading import Thread
from threading import Lock
from time import sleep


class Task1(Thread):
    def run(self):
        while True:
            if lock1.acquire():
                print('-----Task 1-----')
                sleep(0.5)
                lock2.release()


class Task2(Thread):
    def run(self):
        while True:
            if lock2.acquire():
                print('-----Task 2-----')
                sleep(0.5)
                lock3.release()


class Task3(Thread):
    def run(self):
        while True:
            if lock3.acquire():
                print('-----Task 3-----')
                sleep(0.5)
                lock1.release()


lock1 = Lock()

lock2 = Lock()
lock2.acquire()

lock3 = Lock()
lock3.acquire()

t1 = Task1()
t2 = Task2()
t3 = Task3()

t1.start()
t2.start()
t3.start()

​ 運行結果

-----Task 1-----
-----Task 2-----
-----Task 3-----
-----Task 1-----
-----Task 2-----
-----Task 3-----

異步

​ 反之,異步是指當程序 A 調用程序 B 後,A 不會等到 B 執行完再運行,而是繼續向下運行自己的程序

​ 舉個例子就是,A 還是叫 B 去吃飯,B 依然說敲完代碼再去,這時 A 沒有等 B,而是去做自己的事情,等 B 敲完代碼,兩個再一起去吃飯

使用進程池實現進程異步

​ 代碼:

from multiprocessing import Pool
import time
import os


def test():
    print('-----Process in Pool----- pid = %d, ppid = %d' % (os.getpid(), os.getppid()))
    for i in range(3):
        print('----%d----' % i)
        time.sleep(1)
    return 'hahah'


def test2(args):
    print('---callback func---- pid = %d' % os.getpid())
    print('---callback func---- args = %s' % args)


pool = Pool(3)

pool.apply_async(func=test, callback=test2)

time.sleep(5)

print('----mainProcess pid = %d----' % os.getpid())

​ 運行結果:

-----Process in Pool----- pid = 6027, ppid = 6025
----0----
----1----
----2----
---callback func---- pid = 6025
---callback func---- args = hahah
----mainProcess pid = 6025----
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章