多線程產生的問題
併發編程由於需要很多線程、進程之間的協作,所以很容易出現問題,下面主要介紹生產者與消費者問題、死鎖問題、同步和異步問題
生產者與消費者問題
當我們進行一個任務,需要兩個線程不斷地獲取和操作數據時,可能會產生一個問題,如果數據獲取很快,而操作很慢,那麼獲取數據的線程就必須等待操作數據的線程處理完畢,反之如果數據獲取的很慢而操作的很快,那麼操作數據的線程就必須等待獲取數據的線程,這種問題可以看作是一種生產消費能力的不平衡,稱之爲生產者與消費者問題
解決方法
解決這種生產消費能力不平衡的方法就是,在在生產者和消費者中間設立一個緩衝機制,生產者將生產的數據放入緩衝池中,消費者從緩衝池中取出數據進行處理,當緩衝池中的數據量小於一定的值時,生產者就會向緩衝池中添加數據,而當緩衝池中數據大於一個值時,消費者就會從緩衝池中取出數據,使生產者和消費者達到一種動態的平衡
實現代碼:
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----