提高效率
一個人幹活沒有兩個人幹活快,也就是說多線程是爲了更好的幫助程序執行提高效率。
python多線程
總體來說,在python中,實際上並不提倡使用多線程來提高效率,或是說不提倡在CPython中使用多線程,這是因爲在python設計之初的時候,那個年代只有單核cpu,由於硬件發展速度過快,最初設計的爲了保護數據的一種機制,GIL(global interpreter lock),簡單來說就是在python程序執行的開始,底層會鎖住這段程序,以防止數據的混亂,關門,不讓其他程序參與進來,這種機制在硬件發展飛速的今天,成爲了一個遺留問題。
- Cpython應對這種機制辦法:
- 使用多進程 mutiprocess模塊,可以解決,比較喫cpu資源
- 使用其他語言編寫多線程模塊(c語言等),調用api
- 分佈式架構,使用多個虛擬環境同時幹活(Docker)
- 捨棄Cpython解釋器,使用IornPython或是JavaPython
- cpython多線程使用注意事項
- 在使用cpu密集型的代碼時,不建議使用,建議多進程
- 在IO密集型的代碼時,還是可以提高一點效率的。
示例
不使用多線程程序
import time
def func(n):
print("函數開始執行", time.ctime())
time.sleep(n)
print("函數執行結束", time.ctime())
def main():
print("主函數開始執行", time.ctime())
func(2)
func(3)
print("主函數執行結束", time.ctime())
if __name__ == '__main__':
main()
"""
主函數開始執行 Tue Oct 29 13:34:08 2019
函數開始執行 Tue Oct 29 13:34:08 2019
函數執行結束 Tue Oct 29 13:34:10 2019
函數開始執行 Tue Oct 29 13:34:10 2019
函數執行結束 Tue Oct 29 13:34:13 2019
主函數執行結束 Tue Oct 29 13:34:13 2019
"""
"""
在本次程序中,代碼從上到下依次執行,func()第一次執行完畢後,第二次纔開始執行
所以本次程序一共花費了5秒
"""
使用多線程
import time
import threading
def func(n):
print(f"{threading.current_thread().name}-函數開始執行", time.ctime())
time.sleep(n)
print(f"{threading.current_thread().name}函數執行結束", time.ctime())
def main():
print("主函數開始執行", time.ctime())
t1 = threading.Thread(target=func, args=(3,))
t2 = threading.Thread(target=func, args=(2,))
t1.start()
t2.start()
print("主函數執行結束", time.ctime())
if __name__ == '__main__':
main()
"""
主函數開始執行 Tue Oct 29 13:55:41 2019
Thread-1-函數開始執行 Tue Oct 29 13:55:41 2019
Thread-2-函數開始執行 Tue Oct 29 13:55:41 2019
主函數執行結束 Tue Oct 29 13:55:41 2019
Thread-2函數執行結束 Tue Oct 29 13:55:43 2019
Thread-1函數執行結束 Tue Oct 29 13:55:44 2019
"""
"""
本次程序主函數執行花費了0秒,主函數都結束了,但是線程還沒有結束,
這顯然不符合我們的預期,所以,這時候我們需要主函數等待線程執行完畢以後,
在繼續執行。所以就用到了join()方法。
"""
join()
import time
import threading
def func(n):
print(f"{threading.current_thread().name}-函數開始執行", time.ctime())
time.sleep(n)
print(f"{threading.current_thread().name}函數執行結束", time.ctime())
def main():
print("主函數開始執行", time.ctime())
t1 = threading.Thread(target=func, args=(3,))
t2 = threading.Thread(target=func, args=(2,))
t1.start()
t2.start()
t1.join()
t2.join()
print("主函數執行結束", time.ctime())
if __name__ == '__main__':
main()
"""
主函數開始執行 Tue Oct 29 13:58:33 2019
Thread-1-函數開始執行 Tue Oct 29 13:58:33 2019
Thread-2-函數開始執行 Tue Oct 29 13:58:33 2019
Thread-2函數執行結束 Tue Oct 29 13:58:35 2019
Thread-1函數執行結束 Tue Oct 29 13:58:36 2019
主函數執行結束 Tue Oct 29 13:58:36 2019
"""
"""
本次程序主函數執行花費了3秒,線程1花費了3秒,線程2花費了3秒
得出線程1與線程2是同時執行的。
注意:相比於不使用多線程的時候,省了兩秒的時間。
"""
lock()
在上面程序中我們只是看到了時間確實是省了下來,但是因爲並沒有涉及到數據,因此我們現在測試,多線程對數據的影響,儘量不要讓線程對全局變量操作。
不上鎖的情況
因爲我們是使用隨機數來模擬線程的執行,所以不能考慮執行效率
import random
import time
import threading
"""
我們現在使用兩個進程操作同一個數據元素,比如列表,我們希望l1依次添加(0,1,2,3,4,0,1,2,3,4)
一個進程則會添加兩遍,但是爲了效率,我們使用兩個線程同時添加,我們使用time.sleep來模擬線程的動作,
比如說有些線程搶佔的比較快,有些搶佔比較慢。
"""
l1 = []
lock = threading.Lock()
def func(l):
with lock:
for i in range(5):
time.sleep(random.randint(0,2))
l.append(i)
def main():
print("主函數開始執行", time.ctime())
t1 = threading.Thread(target=func, args=(l1,))
t2 = threading.Thread(target=func, args=(l1,))
t1.start()
t2.start()
t1.join()
t2.join()
print(l1)
print("主函數執行結束", time.ctime())
if __name__ == '__main__':
main()
"""
主函數開始執行 Tue Oct 29 14:27:56 2019
[0, 1, 2, 3, 4, 0, 1, 2, 3, 4]
主函數執行結束 Tue Oct 29 14:28:04 2019
"""
"""
從上面可以看出,出現的結果比不是我們想要的【0,1,2,3,4,0,1,2,3,4】
這就是因爲線程的執行順序是無序的。所以門要想控制他們的順序得到我們想要的結果,
這時候lock()就上場了。
"""
上鎖的情況
import random
import time
import threading
"""
我們現在使用兩個進程操作同一個數據元素,比如列表,我們希望l1依次添加(0,1,2,3,4,0,1,2,3,4)
一個進程則會添加兩遍,但是爲了效率,我們使用兩個線程同時添加,我們使用time.sleep來模擬線程的動作,
比如說有些線程搶佔的比較快,有些搶佔比較慢。
"""
l1 = []
lock = threading.Lock() # 創建鎖對象
def func(l):
with lock:
for i in range(5):
time.sleep(random.randint(0,2))
l.append(i)
def main():
print("主函數開始執行", time.ctime())
t1 = threading.Thread(target=func, args=(l1,))
t2 = threading.Thread(target=func, args=(l1,))
t1.start()
t2.start()
t1.join()
t2.join()
print(l1)
print("主函數執行結束", time.ctime())
if __name__ == '__main__':
main()
"""
主函數開始執行 Tue Oct 29 14:27:56 2019
[0, 1, 2, 3, 4, 0, 1, 2, 3, 4]
主函數執行結束 Tue Oct 29 14:28:04 2019
"""
"""
這次我們可以看出輸出符合我們的預期
"""
生產者消費者模式
import random
import threading
import time
gmoney = 1000 # 定義總錢數
lock = threading.Lock() # 創建鎖對象
gTotalTimes = 10 # 生產總次數
gTime = 0 # 記錄生產次數
class Producer(threading.Thread):
def run(self):
global gmoney
global gTime
while True:
with lock:
money = random.randint(100, 1000)
if gTime >= 10: # 當生產了10次,結束程序
break
gmoney += money
print(f"{threading.current_thread().name}生產了{money},總剩餘{gmoney}")
gTime += 1
time.sleep(1)
class Consumer(threading.Thread):
def run(self):
global gmoney
while True:
with lock:
money = random.randint(100, 1000)
if gmoney >= money:
gmoney -= money
print(f"{threading.current_thread().name}消費了{money},總剩餘{gmoney}")
time.sleep(1)
else:
if gTime >= gTotalTimes: # 當生產者不生產的時候,結束
break
def main():
for x in range(3):
t = Consumer(name=f"消費者線程{x}")
t.start()
for x in range(5):
t = Producer(name=f"生產者線程{x}")
t.start()
if __name__ == '__main__':
main()
"""
消費者線程0消費了629,總剩餘371
消費者線程0消費了138,總剩餘233
消費者線程0消費了188,總剩餘45
消費者線程0消費了351,沒錢了
消費者線程0消費了390,沒錢了
消費者線程2消費了722,沒錢了
消費者線程2消費了572,沒錢了
生產者線程1生產了462,總剩餘507
生產者線程1生產了695,總剩餘1202
生產者線程1生產了333,總剩餘1535
生產者線程4生產了834,總剩餘2369
生產者線程4生產了244,總剩餘2613
生產者線程4生產了606,總剩餘3219
生產者線程4生產了592,總剩餘3811
生產者線程4生產了277,總剩餘4088
生產者線程4生產了422,總剩餘4510
生產者線程4生產了345,總剩餘4855
消費者線程1消費了925,總剩餘3930
消費者線程1消費了844,總剩餘3086
消費者線程1消費了967,總剩餘2119
消費者線程1消費了277,總剩餘1842
消費者線程1消費了808,總剩餘1034
消費者線程1消費了878,總剩餘156
消費者線程1消費了117,總剩餘39
"""
condition模式生產者消費者模式
lock上鎖解鎖比較消耗cpu資源,condition就是優化這種模式的
threading.Condition()繼承於threading.Lock()
常用函數說明:
- acquire:上鎖
- release:解鎖
- wait:將當前線程處於等待狀態,並且會釋放鎖,可以被其他線程使用notify和notify_all函數喚醒,被喚醒後,會繼續等待上鎖,上鎖後繼續執行下面的代碼
- notify:通知某個正在等待的線程,默認是第一個等待的線程
- notify_all:通知所有正在等待的線程,notify和notify_all不會釋放鎖,並且需要在release之前調用。
"""
生產者與消費者操作同一個全局變量,gmoney,生產者生產,消費者消費,
當gmoney不足時,就掛起(wait()),等待生產者生產,
當生產者生產完畢以後,就通知(notify_all())消費者可以繼續消費。
相比於LOCK()頻繁上鎖,更節省CPU資源
"""
import random
import threading
import time
gmoney = 1000
condition = threading.Condition()
gTotalTimes = 10
gTime = 0
class Producer(threading.Thread):
def run(self):
global gmoney
global gTime
while True:
money = random.randint(100, 1000)
condition.acquire()
if gTime >= gTotalTimes:
condition.release()
break
gmoney += money
print(f"{threading.current_thread().name}生產了{money},總剩餘{gmoney}")
gTime += 1
condition.notify_all()
condition.release()
time.sleep(1)
class Consumer(threading.Thread):
def run(self):
global gmoney
while True:
money = random.randint(100, 1000)
condition.acquire()
while gmoney <= money:
if gTime >= gTotalTimes:
condition.release()
return
print(f"{threading.current_thread().name}準備消費{money},不足")
condition.wait()
gmoney -= money
print(f"{threading.current_thread().name}消費了{money},剩餘{gmoney}")
condition.release()
time.sleep(1)
def main():
for x in range(3):
t = Consumer(name=f"消費者線程{x}")
t.start()
for x in range(5):
t = Producer(name=f"生產者線程{x}")
t.start()
if __name__ == '__main__':
main()
"""
消費者線程0消費了100,剩餘900
消費者線程1消費了667,剩餘233
消費者線程2準備消費368,不足
生產者線程0生產了981,總剩餘1214
生產者線程1生產了526,總剩餘1740
消費者線程2消費了368,剩餘1372
生產者線程2生產了777,總剩餘2149
生產者線程3生產了915,總剩餘3064
生產者線程4生產了925,總剩餘3989
消費者線程0消費了453,剩餘3536
消費者線程1消費了982,剩餘2554
生產者線程1生產了893,總剩餘3447
生產者線程2生產了492,總剩餘3939
生產者線程0生產了522,總剩餘4461
消費者線程2消費了801,剩餘3660
生產者線程3生產了562,總剩餘4222
生產者線程4生產了500,總剩餘4722
消費者線程1消費了889,剩餘3833
消費者線程0消費了693,剩餘3140
消費者線程2消費了423,剩餘2717
消費者線程0消費了876,剩餘1841
消費者線程1消費了559,剩餘1282
消費者線程2消費了674,剩餘608
消費者線程1消費了592,剩餘16
"""
Queen
線程安全隊列
常用函數說明:
- Queen(maxsize):創建一個先進先出的隊列
- qsize():返回隊列的大小
- empty():判斷是否爲空
- full():判斷是否滿了
- get():取出隊列最後一個數據
- put():見你那個一個數據放到隊列中
可以避免線程爭搶