多任務的概念
什么叫“多任務”呢?簡單地說,就是操作系統可以同時運行多個任務。打個比方,你一邊在用瀏覽器上網,一邊在聽MP3,一邊在用Word趕作業,這就是多任務,至少同時有3個任務正在運行。還有很多任務悄悄地在后台同時運行着,只是桌面上沒有顯示而已。
現在,多核CPU已經非常普及了,但是,即使過去的單核CPU,也可以執行多任務。由於CPU執行代碼都是順序執行的,那么,單核CPU是怎么執行多任務的呢?
答案就是操作系統輪流讓各個任務交替執行,任務1執行0.01秒,切換到任務2,任務2執行0.01秒,再切換到任務3,執行0.01秒……這樣反復執行下去。表面上看,每個任務都是交替執行的,但是,由於CPU的執行速度實在是太快了,我們感覺就像所有任務都在同時執行一樣。
真正的並行執行多任務只能在多核CPU上實現,但是,由於任務數量遠遠多於CPU的核心數量,所以,操作系統也會自動把很多任務輪流調度到每個核心上執行。
注意:
- 並發:指的是任務數多余cpu核數,通過操作系統的各種任務調度算法,實現用多個任務“一起”執行(實際上總有一些任務不在執行,因為切換任務的速度相當快,看上去一起執行而已)
- 並行:指的是任務數小於等於cpu核數,即任務真的是一起執行的
線程基礎
python的thread模塊是比較底層的模塊,python的threading模塊是對thread做了一些包裝的,可以更加方便的被使用
單線程執行
import time
def test():
print("test...")
time.sleep(1)
if __name__ == '__main__':
for i in range(5):
test()
執行效果:程序在控制台每隔一秒打印test...
多線程執行
import time
import threading
def test():
print("test...")
time.sleep(1)
if __name__ == '__main__':
for i in range(5):
t = threading.Thread(target=test)
t.start() # 啟動線程
執行效果:程序在控制台一下子輸出五行test...
,等待1秒左右結束
說明:
- 可以明顯看出使用了多線程並發的操作,花費時間要短很多
- 當調用
start()
時,才會真正的創建線程,並且開始執行
主線程會等待所有子線程結束后才結束
import time
import threading
def playPhone():
print('玩手機...')
time.sleep(1)
def eat():
print("吃東西...")
time.sleep(1)
if __name__ == '__main__':
print("--開始--")
t1 = threading.Thread(target=playPhone)
t1.start()
t2 = threading.Thread(target=eat)
t2.start()
print('--執行結束')
執行效果:主線程阻塞1秒左右后程序結束,說明主線程在等待其他線程執行完畢。
查看線程數量
print('玩手機...')
time.sleep(1)
def eat():
for i in range(10):
print("吃東西...")
time.sleep(1)
if __name__ == '__main__':
print("--開始--")
t1 = threading.Thread(target=playPhone)
t1.start()
t2 = threading.Thread(target=eat)
t2.start()
# 查看正在執行的線程數量
while True:
length = len(threading.enumerate())
print("當前運行的線程數量:%d" % length)
print("這些線程是:%s" % str(threading.enumerate()))
if length == 1:
break
time.sleep(0.5)
print('--執行結束')
在python中,調用threading.enumerate()
能獲取當前正在運行的所有線程,返回值是一個list,調用length()
函數並傳入該list對象就獲取到當前運行線程的數量。
線程-注意點
線程執行代碼的封裝
通過上一篇,能夠看出,通過使用threading模塊能完成多任務的程序開發,為了讓每個線程的封裝性更完美,所以使用threading模塊時,往往會定義一個新的子類class,只要繼承threading.Thread
就可以了,然后重寫run
方法。
示例如下:
import time
import threading
class MyThread(threading.Thread):
def run(self):
for i in range(5):
time.sleep(1)
print("我是%s@%d" % (self.name, i))
if __name__ == "__main__":
mt = MyThread()
mt.start()
運行結果如下:
我是Thread-1@0
我是Thread-1@1
我是Thread-1@2
我是Thread-1@3
我是Thread-1@4
python的threading.Thread類有一個run方法,用於定義線程的功能函數,可以在自己的線程類中覆蓋該方法。而創建自己的線程實例后,通過Thread類的start方法,可以啟動該線程,交給python虛擬機進行調度,當該線程獲得執行的機會時,就會調用run方法執行線程。
線程的執行順序
mport time
import threading
class MyThread(threading.Thread):
def run(self):
for i in range(5):
time.sleep(1)
print("我是%s@%d" % (self.name, i))
if __name__ == "__main__":
for i in range(5):
mt = MyThread()
mt.start()
執行結果(運行的結果可能不一樣,但是大體是一致的):
我是Thread-5@0
我是Thread-2@0
我是Thread-3@0
我是Thread-4@0
我是Thread-1@0
我是Thread-3@1
我是Thread-4@1
我是Thread-2@1
我是Thread-5@1
我是Thread-1@1
我是Thread-3@2
我是Thread-4@2
我是Thread-2@2
我是Thread-5@2
我是Thread-1@2
我是Thread-3@3
我是Thread-4@3
我是Thread-2@3
我是Thread-5@3
我是Thread-1@3
我是Thread-3@4
我是Thread-4@4
我是Thread-5@4
我是Thread-2@4
我是Thread-1@4
從代碼和執行結果我們可以看出,多線程程序的執行順序是不確定的。當執行到sleep語句時,線程將被阻塞(Blocked),到sleep結束后,線程進入就緒(Runnable)狀態,等待調度。而線程調度將自行選擇一個線程執行。上面的代碼中只能保證每個線程都運行完整個run函數,但是線程的啟動順序、run函數中每次循環的執行順序都不能確定。
總結
- 每個線程默認有一個名字,盡管上面的例子中沒有指定線程對象的name,但是python會自動為線程指定一個名字。
- 當線程的run()方法結束時該線程完成。
- 無法控制線程調度程序,但可以通過別的方式來影響線程調度的方式。