線程:
在一個進程的內部,要同時幹多件事,就需要同時運行多個’子任務’,我們把進程內的這些‘子任務’叫做多線程
打個比方,玩LOL吧,它運行是隻算一個進程的,但是它可以鼠標控制,又可以鍵盤控制,這麼多的子任務,就是線程來操作了
-
線程通常叫做
輕型的進程
。線程是共享內存空間的併發執行的多任務,每一個線程都共享一個進程的資源 -
線程是
最小
的執行單元
,而進程由至少一個線程組成,如何調度進程和線程,完全有操作系統決定,程序自己不能決定什麼時候執行,執行多長時間
模塊
:
1._thread
模塊
低級模塊,低級是接近底層,不是很low
2.threading
模塊
高級模塊,對_thread進行了封裝
啓動一個線程
import threading
import time
def run():
print("子線程(%s)開始"% (threading.current_thread().name))
# 實現子線程的功能
print("移動鼠標")
time.sleep(2)
print("子線程(%s)結束" % (threading.current_thread().name))
if __name__ == "__main__":
# 任何進程默認就會啓動一個線程,成爲主線程,主線程可以啓動新的子線程
# current_thread():返回當前線程的實例
print("主線程(%s)啓動" % (threading.current_thread().name))
# 創建子線程
t = threading.Thread(target=run)
# 讓子線程工作
t.start()
print("主線程(%s)結束" % (threading.current_thread().name))
運行結果:
我們發現我們並沒有定義主線程和子線程的名字,這個是默認的,子線程的名字,如果不命名,就默認爲Thread-1、thread-2…
下面我們可以命名子線程的名字
t = threading.Thread(target=run, name="runThread")
運行結果:
我們看到主線程並沒有等待子線程結束之後再結束,但是子線程並沒有任何影響
- 在
Windows
系統中,主線程結束之後,在內存中並沒有銷燬,所以子線程還可以運行 - 但是在
Linux
系統中,主線程結束之後,會立馬在內存中銷燬,所以子線程是不能運行的 - 這兩個區別很大
下面我們來讓主線程等待子線程之後再結束
t.start()
# 等待子線程結束
t.join()
print("主線程(%s)結束" % (threading.current_thread().name))
運行結果:
線程和進程的區別
多線程和多進程最大的不同在於
多進程中
,同一個變量,各自有一份拷貝存在每個進程中,互不影響多線程中
,所有變量都由所有線程共享,所以,任何一個變量都可以被任意一個線程修改,因此,線程之間共享數據最大的危險在於多個線程同時修改一個變量,容易把內容改亂了
線程間共享數據
import threading
import time
num = 10
def run(n):
# 實現子線程的功能
global num
for i in range(1000000):
num += n
num -= n
if __name__ == "__main__":
t1 = threading.Thread(target=run, args=(3,))
t2 = threading.Thread(target=run, args=(5,))
t1.start()
t2.start()
t1.join()
t2.join()
print("num = %s" % (num))
運行結果:
num = 18
理論上結果應該是num = 10
,可是爲什麼不同呢?
原因很簡單,兩個子線程是同時運行的,可能因爲運行的速度快和速度慢的緣故,導致num += n
,或者num -= n
執行的順序不一樣,導致計算產生的偏差
,所以線程之間共享數據是一個十分危險的一件事
線程鎖
線程鎖
:確保了這段代碼從頭到尾只有一個線程在工作
- 阻止了多線程的併發執行,包含鎖的某段代碼實際上只能以單線程模式執行,所以效率大大的降低了
- 不上鎖的其他代碼執行還是多線程,只有有鎖的位置是單線程,所以總歸是比單線程快的
- 由於可以存在多個鎖,不同線程持有不同的鎖,並試圖獲取其他的鎖,可能造成死鎖,導致多個線程掛起。只能靠操作系統強制終止
實例:
import threading
num = 10
"""實例化鎖對象"""
lock = threading.Lock()
def run(n):
"""實現子線程的功能"""
global num
for i in range(1000000):
"""鎖
當線程一到這麼時,會上鎖,然後線程二就不能訪問了
當線程一訪問完時,釋放鎖,相當於解鎖,然後線程二就可以訪問了"""
"""上鎖"""
lock.acquire()
"""程序可能會出錯,所以我們要try一下"""
try:
num += n
num -= n
"""修改完一定要釋放鎖
無論上面的怎麼樣,下面的我們是一定一定要釋放鎖的"""
finally:
lock.release()
"""與上面的代碼功能相同,with lock可以自動上鎖與解鎖"""
"""兩種鎖一次只能選其一"""
with lock:
num += n
num -= n
"""這種方式比上面的方式更安全,死鎖的可能性大大降低"""
if __name__ == "__main__":
t1 = threading.Thread(target=run, args=(3,))
t2 = threading.Thread(target=run, args=(5,))
t1.start()
t2.start()
t1.join()
t2.join()
print("num = %s" % (num))
運行結果:
num = 10
我們這樣纔不會因爲兩個線程的原因產生計算上的錯誤
ThreadLocal
使每個線程都有獨立的儲存空間,對ThreadLocal
對象都可以讀寫,但是互不影響
import threading
num = 0
"""創建一個全局的ThreadLocal對象
每個線程都有獨立的儲存空間
每個線程對ThreadLocal對象都可以讀寫,但是互不影響"""
"""實例化ThreadLocal對象"""
local = threading.local()
def func(n):
"""每個線程都有local.x, 就是線程的局部變量"""
local.x = num
for i in range(1000000):
run(local.x, n)
print("%s--%s" % (threading.current_thread().name, local.x))
def run(x, n):
x += n
x -= n
if __name__ == "__main__":
t1 = threading.Thread(target=func, args=(3,))
t2 = threading.Thread(target=func, args=(5,))
t1.start()
t2.start()
t1.join()
t2.join()
print("num = %s" % (num))
"""作用:爲每個線程去綁定一個數據庫鏈接, 或者是用戶身份信息,或者是HTTP請求
這樣一個線程的所有調用到的處理函數都可以非常方便的訪問這些資源"""
運行結果:
Thread-1--0
Thread-2--0
num = 0
這樣也不會產生影響,挺好
如果有錯誤或者有疑問,請私信我喔,感謝觀看