一.多任務的介紹
學習目標
- 能夠知道多任務的執(zhí)行方式
1. 提問
利用現(xiàn)學知識能夠讓兩個函數(shù)或者方法同時執(zhí)行嗎?
不能,因為之前所寫的程序都是單任務的,也就是說一個函數(shù)或者方法執(zhí)行完成另外一個函數(shù)或者方法才能執(zhí)行,要想實現(xiàn)這種操作就需要使用多任務。
多任務的最大好處是充分利用CPU資源,提高程序的執(zhí)行效率。
2. 多任務的概念
多任務是指在同一時間內(nèi)執(zhí)行多個任務,例如: 現(xiàn)在電腦安裝的操作系統(tǒng)都是多任務操作系統(tǒng),可以同時運行著多個軟件。
多任務效果圖:

3. 多任務的執(zhí)行方式
- 并發(fā)
- 并行
并發(fā):
在一段時間內(nèi)交替去執(zhí)行任務。
例如:
對于單核cpu處理多任務,操作系統(tǒng)輪流讓各個軟件交替執(zhí)行,假如:軟件1執(zhí)行0.01秒,切換到軟件2,軟件2執(zhí)行0.01秒,再切換到軟件3,執(zhí)行0.01秒……這樣反復執(zhí)行下去。表面上看,每個軟件都是交替執(zhí)行的,但是,由于CPU的執(zhí)行速度實在是太快了,我們感覺就像這些軟件都在同時執(zhí)行一樣,這里需要注意單核cpu是并發(fā)的執(zhí)行多任務的。
并行:
對于多核cpu處理多任務,操作系統(tǒng)會給cpu的每個內(nèi)核安排一個執(zhí)行的軟件,多個內(nèi)核是真正的一起執(zhí)行軟件。這里需要注意多核cpu是并行的執(zhí)行多任務,始終有多個軟件一起執(zhí)行。
4. 小結(jié)
- 使用多任務就能充分利用CPU資源,提高程序的執(zhí)行效率,讓你的程序具備處理多個任務的能力。
- 多任務執(zhí)行方式有兩種方式:并發(fā)和并行,這里并行才是多個任務真正意義一起執(zhí)行。
二.進程
學習目標
- 能夠知道進程的作用
1. 進程的介紹
在Python程序中,想要實現(xiàn)多任務可以使用進程來完成,進程是實現(xiàn)多任務的一種方式。
2. 進程的概念
一個正在運行的程序或者軟件就是一個進程,它是操作系統(tǒng)進行資源分配的基本單位,也就是說每啟動一個進程,操作系統(tǒng)都會給其分配一定的運行資源(內(nèi)存資源)保證進程的運行。
比如:現(xiàn)實生活中的公司可以理解成是一個進程,公司提供辦公資源(電腦、辦公桌椅等),真正干活的是員工,員工可以理解成線程。
注意:
一個程序運行后至少有一個進程,一個進程默認有一個線程,進程里面可以創(chuàng)建多個線程,線程是依附在進程里面的,沒有進程就沒有線程。
3. 進程的作用
單進程效果圖:

多進程效果圖:

說明:
多進程可以完成多任務,每個進程就好比一家獨立的公司,每個公司都各自在運營,每個進程也各自在運行,執(zhí)行各自的任務。
4. 小結(jié)
- 進程是操作系統(tǒng)進行資源分配的基本單位。
- 進程是Python程序中實現(xiàn)多任務的一種方式
三.多進程的使用
學習目標
能夠使用多進程完成多任務
1 導入進程包
導入進程包
import multiprocessing
2. Process進程類的說明
Process([group [, target [, name [, args [, kwargs]]]]])
group:指定進程組,目前只能使用None
target:執(zhí)行的目標任務名
name:進程名字
args:以元組方式給執(zhí)行任務傳參
kwargs:以字典方式給執(zhí)行任務傳參
Process創(chuàng)建的實例對象的常用方法:
start():啟動子進程實例(創(chuàng)建子進程)
join():等待子進程執(zhí)行結(jié)束
terminate():不管任務是否完成,立即終止子進程
Process創(chuàng)建的實例對象的常用屬性:
name:當前進程的別名,默認為Process-N,N為從1開始遞增的整數(shù)
3. 多進程完成多任務的代碼
import multiprocessing
import time
# 跳舞任務
def dance():
for i in range(5):
print("跳舞中...")
time.sleep(0.2)
# 唱歌任務
def sing():
for i in range(5):
print("唱歌中...")
time.sleep(0.2)
if __name__ == '__main__':
# 創(chuàng)建跳舞的子進程
# group: 表示進程組,目前只能使用None
# target: 表示執(zhí)行的目標任務名(函數(shù)名、方法名)
# name: 進程名稱, 默認是Process-1, .....
dance_process = multiprocessing.Process(target=dance, name="myprocess1")
sing_process = multiprocessing.Process(target=sing)
# 啟動子進程執(zhí)行對應的任務
dance_process.start()
sing_process.start()
執(zhí)行結(jié)果:
唱歌中...
跳舞中...
唱歌中...
跳舞中...
唱歌中...
跳舞中...
唱歌中...
跳舞中...
唱歌中...
跳舞中...
4. 小結(jié)
導入進程包
import multiprocessing
創(chuàng)建子進程并指定執(zhí)行的任務
sub_process = multiprocessing.Process (target=任務名)
啟動進程執(zhí)行任務
sub_process.start()
四.取得進程編號
學習目標
能夠知道如果取得進程編號
1. 取得進程編號的目的
取得進程編號的目的是驗證主進程和子進程的關(guān)系,可以得知子進程是由哪個主進程創(chuàng)建出來的。
取得進程編號的兩種操作
1.取得當前進程編號
2.取得當前父進程編號
2. 取得當前進程編號
os.getpid() 表示取得當前進程編號
示例代碼:
import multiprocessing
import time
import os
# 跳舞任務
def dance():
# 取得當前進程的編號
print("dance:", os.getpid())
# 取得當前進程
print("dance:", multiprocessing.current_process())
for i in range(5):
print("跳舞中...")
time.sleep(0.2)
# 擴展:根據(jù)進程編號殺死指定進程
os.kill(os.getpid(), 9)
# 唱歌任務
def sing():
# 取得當前進程的編號
print("sing:", os.getpid())
# 取得當前進程
print("sing:", multiprocessing.current_process())
for i in range(5):
print("唱歌中...")
time.sleep(0.2)
if __name__ == '__main__':
# 取得當前進程的編號
print("main:", os.getpid())
# 取得當前進程
print("main:", multiprocessing.current_process())
# 創(chuàng)建跳舞的子進程
# group: 表示進程組,目前只能使用None
# target: 表示執(zhí)行的目標任務名(函數(shù)名、方法名)
# name: 進程名稱, 默認是Process-1, .....
dance_process = multiprocessing.Process(target=dance, name="myprocess1")
sing_process = multiprocessing.Process(target=sing)
# 啟動子進程執(zhí)行對應的任務
dance_process.start()
sing_process.start()
執(zhí)行結(jié)果:
main: 70763
main: <_MainProcess(MainProcess, started)>
dance: 70768
dance: <Process(myprocess1, started)>
跳舞中...
sing: 70769
sing: <Process(Process-2, started)>
唱歌中...
唱歌中...
唱歌中...
唱歌中...
唱歌中...
3. 取得當前父進程編號
os.getppid() 表示取得當前父進程編號
示例代碼:
import multiprocessing
import time
import os
跳舞任務
def dance():
# 取得當前進程的編號
print("dance:", os.getpid())
# 取得當前進程
print("dance:", multiprocessing.current_process())
# 取得父進程的編號
print("dance的父進程編號:", os.getppid())
for i in range(5):
print("跳舞中...")
time.sleep(0.2)
# 擴展:根據(jù)進程編號殺死指定進程
os.kill(os.getpid(), 9)
# 唱歌任務
def sing():
# 取得當前進程的編號
print("sing:", os.getpid())
# 取得當前進程
print("sing:", multiprocessing.current_process())
# 取得父進程的編號
print("sing的父進程編號:", os.getppid())
for i in range(5):
print("唱歌中...")
time.sleep(0.2)
if __name__ == '__main__':
# 取得當前進程的編號
print("main:", os.getpid())
# 取得當前進程
print("main:", multiprocessing.current_process())
# 創(chuàng)建跳舞的子進程
# group: 表示進程組,目前只能使用None
# target: 表示執(zhí)行的目標任務名(函數(shù)名、方法名)
# name: 進程名稱, 默認是Process-1, .....
dance_process = multiprocessing.Process(target=dance, name="myprocess1")
sing_process = multiprocessing.Process(target=sing)
# 啟動子進程執(zhí)行對應的任務
dance_process.start()
sing_process.start()
運行結(jié)果:
main: 70860
main: <_MainProcess(MainProcess, started)>
dance: 70861
dance: <Process(myprocess1, started)>
dance的父進程編號: 70860
跳舞中...
sing: 70862
sing: <Process(Process-2, started)>
sing的父進程編號: 70860
唱歌中...
唱歌中...
唱歌中...
唱歌中...
唱歌中...
4. 小結(jié)
取得當前進程編號
os.getpid()
取得當前父進程編號
os.getppid()
取得進程編號可以查看父子進程的關(guān)系
五.進程執(zhí)行帶有參數(shù)的任務
學習目標
能夠?qū)懗鲞M程執(zhí)行帶有參數(shù)的任務
1. 進程執(zhí)行帶有參數(shù)的任務的介紹
前面我們使用進程執(zhí)行的任務是沒有參數(shù)的,假如我們使用進程執(zhí)行的任務帶有參數(shù),如何給函數(shù)傳參呢?
Process類執(zhí)行任務并給任務傳參數(shù)有兩種方式:
1.args 表示以元組的方式給執(zhí)行任務傳參
2.kwargs 表示以字典方式給執(zhí)行任務傳參
2. args參數(shù)的使用
示例代碼:
import multiprocessing
import time
# 帶有參數(shù)的任務
def task(count):
for i in range(count):
print("任務執(zhí)行中..")
time.sleep(0.2)
else:
print("任務執(zhí)行完成")
if __name__ == '__main__':
# 創(chuàng)建子進程
# args: 以元組的方式給任務傳入?yún)?shù)
sub_process = multiprocessing.Process(target=task, args=(5,))
sub_process.start()
執(zhí)行結(jié)果:
任務執(zhí)行中..
任務執(zhí)行中..
任務執(zhí)行中..
任務執(zhí)行中..
任務執(zhí)行中..
任務執(zhí)行完成
3. kwargs參數(shù)的使用
示例代碼:
import multiprocessing
import time
# 帶有參數(shù)的任務
def task(count):
for i in range(count):
print("任務執(zhí)行中..")
time.sleep(0.2)
else:
print("任務執(zhí)行完成")
if __name__ == '__main__':
# 創(chuàng)建子進程
# kwargs: 表示以字典方式傳入?yún)?shù)
sub_process = multiprocessing.Process(target=task, kwargs={"count": 3})
sub_process.start()
執(zhí)行結(jié)果:
任務執(zhí)行中..
任務執(zhí)行中..
任務執(zhí)行中..
任務執(zhí)行完成
4. 小結(jié)
進程執(zhí)行任務并傳參有兩種方式:
1.元組方式傳參(args): 元組方式傳參一定要和參數(shù)的順序保持一致。
2.字典方式傳參(kwargs): 字典方式傳參字典中的key一定要和參數(shù)名保持一致。
六.進程的注意點
學習目標
- 能夠說出進程的注意點
1. 進程的注意點介紹
- 進程之間不共享全局變量
- 主進程會等待所有的子進程執(zhí)行結(jié)束再結(jié)束
2. 進程之間不共享全局變量
import multiprocessing
import time
# 定義全局變量
g_list = list()
# 添加數(shù)據(jù)的任務
def add_data():
for i in range(5):
g_list.append(i)
print("add:", i)
time.sleep(0.2)
# 代碼執(zhí)行到此,說明數(shù)據(jù)添加完成
print("add_data:", g_list)
def read_data():
print("read_data", g_list)
if __name__ == '__main__':
# 創(chuàng)建添加數(shù)據(jù)的子進程
add_data_process = multiprocessing.Process(target=add_data)
# 創(chuàng)建讀取數(shù)據(jù)的子進程
read_data_process = multiprocessing.Process(target=read_data)
# 啟動子進程執(zhí)行對應的任務
add_data_process.start()
# 主進程等待添加數(shù)據(jù)的子進程執(zhí)行完成以后程序再繼續(xù)往下執(zhí)行,讀取數(shù)據(jù)
add_data_process.join()
read_data_process.start()
print("main:", g_list)
# 總結(jié): 多進程之間不共享全局變量
執(zhí)行結(jié)果:
add: 0
add: 1
add: 2
add: 3
add: 4
add_data: [0, 1, 2, 3, 4]
main: []
read_data []
進程之間不共享全局變量的解釋效果圖:

3. 進程之間不共享全局變量的小結(jié)
- 創(chuàng)建子進程會對主進程資源進行拷貝,也就是說子進程是主進程的一個副本,好比是一對雙胞胎,之所以進程之間不共享全局變量,是因為操作的不是同一個進程里面的全局變量,只不過不同進程里面的全局變量名字相同而已。
4. 主進程會等待所有的子進程執(zhí)行結(jié)束再結(jié)束
假如我們現(xiàn)在創(chuàng)建一個子進程,這個子進程執(zhí)行完大概需要2秒鐘,現(xiàn)在讓主進程執(zhí)行0.5秒鐘就退出程序,查看一下執(zhí)行結(jié)果,示例代碼如下:
import multiprocessing
import time
# 定義進程所需要執(zhí)行的任務
def task():
for i in range(10):
print("任務執(zhí)行中...")
time.sleep(0.2)
if __name__ == '__main__':
# 創(chuàng)建子進程
sub_process = multiprocessing.Process(target=task)
sub_process.start()
# 主進程延時0.5秒鐘
time.sleep(0.5)
print("over")
exit()
# 總結(jié): 主進程會等待所有的子進程執(zhí)行完成以后程序再退出
執(zhí)行結(jié)果:
任務執(zhí)行中...
任務執(zhí)行中...
任務執(zhí)行中...
over
任務執(zhí)行中...
任務執(zhí)行中...
任務執(zhí)行中...
任務執(zhí)行中...
任務執(zhí)行中...
任務執(zhí)行中...
任務執(zhí)行中...
說明:
通過上面代碼的執(zhí)行結(jié)果,我們可以得知: 主進程會等待所有的子進程執(zhí)行結(jié)束再結(jié)束
假如我們就讓主進程執(zhí)行0.5秒鐘,子進程就銷毀不再執(zhí)行,那怎么辦呢?
- 我們可以設置守護主進程 或者 在主進程退出之前 讓子進程銷毀
守護主進程:
- 守護主進程就是主進程退出子進程銷毀不再執(zhí)行
子進程銷毀:
- 子進程執(zhí)行結(jié)束
保證主進程正常退出的示例代碼:
import multiprocessing
import time
# 定義進程所需要執(zhí)行的任務
def task():
for i in range(10):
print("任務執(zhí)行中...")
time.sleep(0.2)
if __name__ == '__main__':
# 創(chuàng)建子進程
sub_process = multiprocessing.Process(target=task)
# 設置守護主進程,主進程退出子進程直接銷毀,子進程的生命周期依賴與主進程
# sub_process.daemon = True
sub_process.start()
time.sleep(0.5)
print("over")
# 讓子進程銷毀
sub_process.terminate()
exit()
# 總結(jié): 主進程會等待所有的子進程執(zhí)行完成以后程序再退出
# 如果想要主進程退出子進程銷毀,可以設置守護主進程或者在主進程退出之前讓子進程銷毀
執(zhí)行結(jié)果:
任務執(zhí)行中...
任務執(zhí)行中...
任務執(zhí)行中...
over
5. 主進程會等待所有的子進程執(zhí)行結(jié)束再結(jié)束的小結(jié)
- 為了保證子進程能夠正常的運行,主進程會等所有的子進程執(zhí)行完成以后再銷毀,設置守護主進程的目的是主進程退出子進程銷毀,不讓主進程再等待子進程去執(zhí)行。
- 設置守護主進程方式: 子進程對象.daemon = True
- 銷毀子進程方式: 子進程對象.terminate()
七.線程
學習目標
能夠知道線程的作用
1. 線程的介紹
在Python中,想要實現(xiàn)多任務除了使用進程,還可以使用線程來完成,線程是實現(xiàn)多任務的另外一種方式。
2. 線程的概念
線程是進程中執(zhí)行代碼的一個分支,每個執(zhí)行分支(線程)要想工作執(zhí)行代碼需要cpu進行調(diào)度 ,也就是說線程是cpu調(diào)度的基本單位,每個進程至少都有一個線程,而這個線程就是我們通常說的主線程。
3. 線程的作用
多線程可以完成多任務
多線程效果圖:

4. 小結(jié)
- 線程是Python程序中實現(xiàn)多任務的另外一種方式,線程的執(zhí)行需要cpu調(diào)度來完成。
八.多線程的使用
學習目標
能夠使用多線程完成多任務
1. 導入線程模塊
#導入線程模塊
import threading
2. 線程類Thread參數(shù)說明
Thread([group [, target [, name [, args [, kwargs]]]]])
group: 線程組,目前只能使用None
target: 執(zhí)行的目標任務名
args: 以元組的方式給執(zhí)行任務傳參
kwargs: 以字典方式給執(zhí)行任務傳參
name: 線程名,一般不用設置
3. 啟動線程
啟動線程使用start方法
4. 多線程完成多任務的代碼
import threading
import time
# 唱歌任務
def sing():
# 擴展: 獲取當前線程
# print("sing當前執(zhí)行的線程為:", threading.current_thread())
for i in range(3):
print("正在唱歌...%d" % i)
time.sleep(1)
# 跳舞任務
def dance():
# 擴展: 獲取當前線程
# print("dance當前執(zhí)行的線程為:", threading.current_thread())
for i in range(3):
print("正在跳舞...%d" % i)
time.sleep(1)
if __name__ == '__main__':
# 擴展: 獲取當前線程
# print("當前執(zhí)行的線程為:", threading.current_thread())
# 創(chuàng)建唱歌的線程
# target: 線程執(zhí)行的函數(shù)名
sing_thread = threading.Thread(target=sing)
# 創(chuàng)建跳舞的線程
dance_thread = threading.Thread(target=dance)
# 開啟線程
sing_thread.start()
dance_thread.start()
執(zhí)行結(jié)果:
正在唱歌...0
正在跳舞...0
正在唱歌...1
正在跳舞...1
正在唱歌...2
正在跳舞...2
5. 小結(jié)
導入線程模塊
import threading
創(chuàng)建子線程并指定執(zhí)行的任務
sub_thread = threading.Thread(target=任務名)
啟動線程執(zhí)行任務
sub_thread.start()
九.線程執(zhí)行帶有參數(shù)的任務
學習目標
能夠?qū)懗鼍€程執(zhí)行帶有參數(shù)的任務
1. 線程執(zhí)行帶有參數(shù)的任務的介紹
前面我們使用線程執(zhí)行的任務是沒有參數(shù)的,假如我們使用線程執(zhí)行的任務帶有參數(shù),如何給函數(shù)傳參呢?
Thread類執(zhí)行任務并給任務傳參數(shù)有兩種方式:
args 表示以元組的方式給執(zhí)行任務傳參
kwargs 表示以字典方式給執(zhí)行任務傳參
2. args參數(shù)的使用
示例代碼:
import threading
import time
# 帶有參數(shù)的任務
def task(count):
for i in range(count):
print("任務執(zhí)行中..")
time.sleep(0.2)
else:
print("任務執(zhí)行完成")
if __name__ == '__main__':
# 創(chuàng)建子線程
# args: 以元組的方式給任務傳入?yún)?shù)
sub_thread = threading.Thread(target=task, args=(5,))
sub_thread.start()
執(zhí)行結(jié)果:
任務執(zhí)行中..
任務執(zhí)行中..
任務執(zhí)行中..
任務執(zhí)行中..
任務執(zhí)行中..
任務執(zhí)行完成
3. kwargs參數(shù)的使用
示例代碼:
import threading
import time
# 帶有參數(shù)的任務
def task(count):
for i in range(count):
print("任務執(zhí)行中..")
time.sleep(0.2)
else:
print("任務執(zhí)行完成")
if __name__ == '__main__':
# 創(chuàng)建子線程
# kwargs: 表示以字典方式傳入?yún)?shù)
sub_thread = threading.Thread(target=task, kwargs={"count": 3})
sub_thread.start()
執(zhí)行結(jié)果:
任務執(zhí)行中..
任務執(zhí)行中..
任務執(zhí)行中..
任務執(zhí)行完成
4. 小結(jié)
線程執(zhí)行任務并傳參有兩種方式:
元組方式傳參(args) :元組方式傳參一定要和參數(shù)的順序保持一致。
字典方式傳參(kwargs):字典方式傳參字典中的key一定要和參數(shù)名保持一致。
十.線程的注意點
學習目標
能夠說出線程的注意點
1. 線程的注意點介紹
- 線程之間執(zhí)行是無序的
主線程會等待所有的子線程執(zhí)行結(jié)束再結(jié)束 - 線程之間共享全局變量
線程之間共享全局變量數(shù)據(jù)出現(xiàn)錯誤問題
2. 線程之間執(zhí)行是無序的
import threading
import time
def task():
time.sleep(1)
print("當前線程:", threading.current_thread().name)
if __name__ == '__main__':
for _ in range(5):
sub_thread = threading.Thread(target=task)
sub_thread.start()
執(zhí)行結(jié)果:
當前線程: Thread-1
當前線程: Thread-2
當前線程: Thread-4
當前線程: Thread-5
當前線程: Thread-3
說明:
線程之間執(zhí)行是無序的,它是由cpu調(diào)度決定的 ,cpu調(diào)度哪個線程,哪個線程就先執(zhí)行,沒有調(diào)度的線程不能執(zhí)行。
進程之間執(zhí)行也是無序的,它是由操作系統(tǒng)調(diào)度決定的,操作系統(tǒng)調(diào)度哪個進程,哪個進程就先執(zhí)行,沒有調(diào)度的進程不能執(zhí)行。
3. 主線程會等待所有的子線程執(zhí)行結(jié)束再結(jié)束
假如我們現(xiàn)在創(chuàng)建一個子線程,這個子線程執(zhí)行完大概需要2.5秒鐘,現(xiàn)在讓主線程執(zhí)行1秒鐘就退出程序,查看一下執(zhí)行結(jié)果,示例代碼如下:
import threading
import time
# 測試主線程是否會等待子線程執(zhí)行完成以后程序再退出
def show_info():
for i in range(5):
print("test:", i)
time.sleep(0.5)
if __name__ == '__main__':
sub_thread = threading.Thread(target=show_info)
sub_thread.start()
# 主線程延時1秒
time.sleep(1)
print("over")
執(zhí)行結(jié)果:
test: 0
test: 1
over
test: 2
test: 3
test: 4
說明:
通過上面代碼的執(zhí)行結(jié)果,我們可以得知: 主線程會等待所有的子線程執(zhí)行結(jié)束再結(jié)束
假如我們就讓主線程執(zhí)行1秒鐘,子線程就銷毀不再執(zhí)行,那怎么辦呢?
我們可以設置守護主線程
守護主線程:
守護主線程就是主線程退出子線程銷毀不再執(zhí)行
設置守護主線程有兩種方式:
threading.Thread(target=show_info, daemon=True)
線程對象.setDaemon(True)
設置守護主線程的示例代碼:
import threading
import time
# 測試主線程是否會等待子線程執(zhí)行完成以后程序再退出
def show_info():
for i in range(5):
print("test:", i)
time.sleep(0.5)
if __name__ == '__main__':
# 創(chuàng)建子線程守護主線程
# daemon=True 守護主線程
# 守護主線程方式1
sub_thread = threading.Thread(target=show_info, daemon=True)
# 設置成為守護主線程,主線程退出后子線程直接銷毀不再執(zhí)行子線程的代碼
# 守護主線程方式2
# sub_thread.setDaemon(True)
sub_thread.start()
# 主線程延時1秒
time.sleep(1)
print("over")
執(zhí)行結(jié)果:
test: 0
test: 1
over
3. 線程之間共享全局變量
需求:
定義一個列表類型的全局變量
創(chuàng)建兩個子線程分別執(zhí)行向全局變量添加數(shù)據(jù)的任務和向全局變量讀取數(shù)據(jù)的
任務
查看線程之間是否共享全局變量數(shù)據(jù)
import threading
import time
# 定義全局變量
my_list = list()
# 寫入數(shù)據(jù)任務
def write_data():
for i in range(5):
my_list.append(i)
time.sleep(0.1)
print("write_data:", my_list)
# 讀取數(shù)據(jù)任務
def read_data():
print("read_data:", my_list)
if __name__ == '__main__':
# 創(chuàng)建寫入數(shù)據(jù)的線程
write_thread = threading.Thread(target=write_data)
# 創(chuàng)建讀取數(shù)據(jù)的線程
read_thread = threading.Thread(target=read_data)
write_thread.start()
# 延時
# time.sleep(1)
# 主線程等待寫入線程執(zhí)行完成以后代碼在繼續(xù)往下執(zhí)行
write_thread.join()
print("開始讀取數(shù)據(jù)啦")
read_thread.start()
執(zhí)行結(jié)果:
write_data: [0, 1, 2, 3, 4]
開始讀取數(shù)據(jù)啦
read_data: [0, 1, 2, 3, 4]
4. 線程之間共享全局變量數(shù)據(jù)出現(xiàn)錯誤問題
需求:
定義兩個函數(shù),實現(xiàn)循環(huán)100萬次,每循環(huán)一次給全局變量加1
創(chuàng)建兩個子線程執(zhí)行對應的兩個函數(shù),查看計算后的結(jié)果
import threading
# 定義全局變量
g_num = 0
# 循環(huán)一次給全局變量加1
def sum_num1():
for i in range(1000000):
global g_num
g_num += 1
print("sum1:", g_num)
# 循環(huán)一次給全局變量加1
def sum_num2():
for i in range(1000000):
global g_num
g_num += 1
print("sum2:", g_num)
if __name__ == '__main__':
# 創(chuàng)建兩個線程
first_thread = threading.Thread(target=sum_num1)
second_thread = threading.Thread(target=sum_num2)
# 啟動線程
first_thread.start()
# 啟動線程
second_thread.start()
執(zhí)行結(jié)果:
sum1: 1210949
sum2: 1496035
注意點:
多線程同時對全局變量操作數(shù)據(jù)發(fā)生了錯誤
- 錯誤分析:
兩個線程first_thread和second_thread都要對全局變量g_num(默認是0)進行加1運算,但是由于是多線程同時操作,有可能出現(xiàn)下面情況:
在g_num=0時,first_thread取得g_num=0。此時系統(tǒng)把first_thread調(diào)度為”sleeping”狀態(tài),把second_thread轉(zhuǎn)換為”running”狀態(tài),t2也獲得g_num=0
然后second_thread對得到的值進行加1并賦給g_num,使得g_num=1
然后系統(tǒng)又把second_thread調(diào)度為”sleeping”,把first_thread轉(zhuǎn)為”running”。線程t1又把它之前得到的0加1后賦值給g_num。
這樣導致雖然first_thread和first_thread都對g_num加1,但結(jié)果仍然是g_num=1 - 全局變量數(shù)據(jù)錯誤的解決辦法:
線程同步: 保證同一時刻只能有一個線程去操作全局變量 同步: 就是協(xié)同步調(diào),按預定的先后次序進行運行。如:你說完,我再說, 好比現(xiàn)實生活中的對講機
線程同步的方式:
線程等待(join)
互斥鎖
線程等待的示例代碼:
import threading
# 定義全局變量
g_num = 0
# 循環(huán)1000000次每次給全局變量加1
def sum_num1():
for i in range(1000000):
global g_num
g_num += 1
print("sum1:", g_num)
# 循環(huán)1000000次每次給全局變量加1
def sum_num2():
for i in range(1000000):
global g_num
g_num += 1
print("sum2:", g_num)
if __name__ == '__main__':
# 創(chuàng)建兩個線程
first_thread = threading.Thread(target=sum_num1)
second_thread = threading.Thread(target=sum_num2)
# 啟動線程
first_thread.start()
# 主線程等待第一個線程執(zhí)行完成以后代碼再繼續(xù)執(zhí)行,讓其執(zhí)行第二個線程
# 線程同步: 一個任務執(zhí)行完成以后另外一個任務才能執(zhí)行,同一個時刻只有一個任務在執(zhí)行
first_thread.join()
# 啟動線程
second_thread.start()
執(zhí)行結(jié)果:
sum1: 1000000
sum2: 2000000
5. 小結(jié)
- 線程執(zhí)行執(zhí)行是無序的
主線程默認會等待所有子線程執(zhí)行結(jié)束再結(jié)束,設置守護主線程的目的是主線程退出子線程銷毀。 - 線程之間共享全局變量,好處是可以對全局變量的數(shù)據(jù)進行共享。
線程之間共享全局變量可能會導致數(shù)據(jù)出現(xiàn)錯誤問題,可以使用線程同步方式來解決這個問題。 - 線程等待(join)
十一.互斥鎖
學習目標
能夠知道互斥鎖的作用
1.互斥鎖的概念
互斥鎖: 對共享數(shù)據(jù)進行鎖定,保證同一時刻只能有一個線程去操作。
注意:
互斥鎖是多個線程一起去搶,搶到鎖的線程先執(zhí)行,沒有搶到鎖的線程需要等待,等互斥鎖使用完釋放后,其它等待的線程再去搶這個鎖。
3. 互斥鎖的使用
threading模塊中定義了Lock變量,這個變量本質(zhì)上是一個函數(shù),通過調(diào)用這個函數(shù)可以獲取一把互斥鎖。
互斥鎖使用步驟:
# 創(chuàng)建鎖
mutex = threading.Lock()
# 上鎖
mutex.acquire()
#...這里編寫代碼能保證同一時刻只能有一個線程去操作, 對共享數(shù)據(jù)進行鎖定...
# 釋放鎖
mutex.release()
注意點:
acquire和release方法之間的代碼同一時刻只能有一個線程去操作
如果在調(diào)用acquire方法的時候 其他線程已經(jīng)使用了這個互斥鎖,那么此時acquire方法會堵塞,直到這個互斥鎖釋放后才能再次上鎖。
4. 使用互斥鎖完成2個線程對同一個全局變量各加100萬次的操作
import threading
# 定義全局變量
g_num = 0
# 創(chuàng)建全局互斥鎖
lock = threading.Lock()
# 循環(huán)一次給全局變量加1
def sum_num1():
# 上鎖
lock.acquire()
for i in range(1000000):
global g_num
g_num += 1
print("sum1:", g_num)
# 釋放鎖
lock.release()
# 循環(huán)一次給全局變量加1
def sum_num2():
# 上鎖
lock.acquire()
for i in range(1000000):
global g_num
g_num += 1
print("sum2:", g_num)
# 釋放鎖
lock.release()
if __name__ == '__main__':
# 創(chuàng)建兩個線程
first_thread = threading.Thread(target=sum_num1)
second_thread = threading.Thread(target=sum_num2)
# 啟動線程
first_thread.start()
second_thread.start()
# 提示:加上互斥鎖,那個線程搶到這個鎖我們決定不了,那線程搶到鎖那個線程先執(zhí)行,沒有搶到的線程需要等待
# 加上互斥鎖多任務瞬間變成單任務,性能會下降,也就是說同一時刻只能有一個線程去執(zhí)行
執(zhí)行結(jié)果:
sum1: 1000000
sum2: 2000000
說明:
通過執(zhí)行結(jié)果可以地址互斥鎖能夠保證多個線程訪問共享數(shù)據(jù)不會出現(xiàn)數(shù)據(jù)錯誤問題
5. 小結(jié)
- 互斥鎖的作用就是保證同一時刻只能有一個線程去操作共享數(shù)據(jù),保證共享數(shù)據(jù)不會出現(xiàn)錯誤問題
- 使用互斥鎖的好處確保某段關(guān)鍵代碼只能由一個線程從頭到尾完整地去執(zhí)行
- 使用互斥鎖會影響代碼的執(zhí)行效率,多任務改成了單任務執(zhí)行
- 互斥鎖如果沒有使用好容易出現(xiàn)死鎖的情況
十二.死鎖
學習目標
- 能夠知道產(chǎn)生死鎖的原因
1. 死鎖的概念
死鎖: 一直等待對方釋放鎖的情景就是死鎖
死鎖的結(jié)果
- 會造成應用程序的停止響應,不能再處理其它任務了。
2. 死鎖示例
需求:
根據(jù)下標在列表中取值, 保證同一時刻只能有一個線程去取值
import threading
import time
# 創(chuàng)建互斥鎖
lock = threading.Lock()
# 根據(jù)下標去取值, 保證同一時刻只能有一個線程去取值
def get_value(index):
# 上鎖
lock.acquire()
print(threading.current_thread())
my_list = [3,6,8,1]
# 判斷下標釋放越界
if index >= len(my_list):
print("下標越界:", index)
return
value = my_list[index]
print(value)
time.sleep(0.2)
# 釋放鎖
lock.release()
if __name__ == '__main__':
# 模擬大量線程去執(zhí)行取值操作
for i in range(30):
sub_thread = threading.Thread(target=get_value, args=(i,))
sub_thread.start()
3. 避免死鎖
- 在合適的地方釋放鎖
import threading
import time
# 創(chuàng)建互斥鎖
lock = threading.Lock()
# 根據(jù)下標去取值, 保證同一時刻只能有一個線程去取值
def get_value(index):
# 上鎖
lock.acquire()
print(threading.current_thread())
my_list = [3,6,8,1]
if index >= len(my_list):
print("下標越界:", index)
# 當下標越界需要釋放鎖,讓后面的線程還可以取值
lock.release()
return
value = my_list[index]
print(value)
time.sleep(0.2)
# 釋放鎖
lock.release()
if __name__ == '__main__':
# 模擬大量線程去執(zhí)行取值操作
for i in range(30):
sub_thread = threading.Thread(target=get_value, args=(i,))
sub_thread.start()
4. 小結(jié)
- 使用互斥鎖的時候需要注意死鎖的問題,要在合適的地方注意釋放鎖。
- 死鎖一旦產(chǎn)生就會造成應用程序的停止響應,應用程序無法再繼續(xù)往下執(zhí)行了。
十三.進程和線程的對比
學習目標
- 能夠知道進程和線程的關(guān)系
1. 進程和線程的對比的三個方向
- 關(guān)系對比
- 區(qū)別對比
- 優(yōu)缺點對比
2. 關(guān)系對比
- 線程是依附在進程里面的,沒有進程就沒有線程。
- 一個進程默認提供一條線程,進程可以創(chuàng)建多個線程。

2. 區(qū)別對比
進程之間不共享全局變量
線程之間共享全局變量,但是要注意資源競爭的問題,解決辦法: 互斥鎖或者線程同步
創(chuàng)建進程的資源開銷要比創(chuàng)建線程的資源開銷要大
進程是操作系統(tǒng)資源分配的基本單位,線程是CPU調(diào)度的基本單位
線程不能夠獨立執(zhí)行,必須依存在進程中
多進程開發(fā)比單進程多線程開發(fā)穩(wěn)定性要強
3. 優(yōu)缺點對比
- 進程優(yōu)缺點:
優(yōu)點:可以用多核
缺點:資源開銷大
- 線程優(yōu)缺點:
優(yōu)點:資源開銷小
缺點:不能使用多核
4. 小結(jié)
- 進程和線程都是完成多任務的一種方式
- 多進程要比多線程消耗的資源多,但是多進程開發(fā)比單進程多線程開發(fā)穩(wěn)定性要強,某個進程掛掉不會影響其它進程。
- 多進程可以使用cpu的多核運行,多線程可以共享全局變量。
- 線程不能單獨執(zhí)行必須依附在進程里面