是麼是多任務呢? 我如今聽着音樂,同時瀏覽着網頁,在文檔中寫着筆記.是的,這就是多任務;對於計算機來講,就是同時執行多段的代碼;python
現在計算機都是多核CPU了,單核CPU也能夠執行多任務,咱們都知道計算機的代碼都是順序執行的,那麼,單核的CPU是如何實現多任務的呢?安全
答案就是在極短的時間內輪流切換各個任務,CPU的運算太快了,給咱們的感受就是在同時進行同樣;bash
這裏引進兩個概念:並行和併發多線程
併發: 例如在單核CPU中執行多個任務,這個就是併發(執行的任務數量大於CPU核數)併發
並行: 兩個任務在多核CPU機器中執行,兩個任務分別在不一樣的CPU中執行,這個就是並行(任務數小於CPU核數)app
例如: 學校運動會中,5000米決賽都是十幾我的,起跑時,人數多於賽道,那麼這種狀況就是併發;100決賽都是武林高手,待遇不一樣,每人一個跑道,這個就是並行.ide
在python3中,線程由threading模塊提供,來一窺threading面貌函數
threading模塊下經常使用的方法或者屬性學習
方法 | 說明 |
---|---|
current_thread() | 返回當前線程 |
active_count() | 返回當前活躍的線程數量,主線程+子線程 |
get_ident() | 返回當前線程 |
enumerate() | 返回當前活動的Thread列表 |
main_thread() | 返回主Thread對象 |
settrace(func) | 爲全部線程設置一個 trace 函數 |
setprofile(func) | 爲全部線程設置一個 profile 函數 |
stack_size([size]) | 返回新建立線程棧大小;或爲後續建立的線程設定棧大小爲 size |
TIMEOUT_MAX | Lock.acquire(), RLock.acquire(), Condition.wait() 容許的最大超時時間 |
threading模塊包含的類測試
類 | 說明 |
---|---|
Thread | 基本的線程類 |
Lock | 互斥鎖 |
RLock | 可重入鎖,使單一進程再次得到已持有的鎖(遞歸鎖) |
Condition | 條件鎖,使得一個線程等待另外一個線程知足特定條件,好比改變狀態或某個值 |
Semaphore | 信號鎖。爲線程間共享的有限資源提供一個」計數器」,若是沒有可用資源則會被阻塞 |
Event | 事件鎖,任意數量的線程等待某個事件的發生,在該事件發生後全部線程被激活 |
Timer | 一種計時器 |
Barrier | Python3.2新增的「阻礙」類,必須達到指定數量的線程後才能夠繼續執行 |
threading模塊中Thread類的方法和屬性
方法與屬性 | 說明 |
---|---|
start() | 啓動線程,等待CPU調度 |
run() | 線程被cpu調度後自動執行的方法 |
getName()、setName()和name | 用於獲取和設置線程的名稱 |
setDaemon() | 設置爲後臺線程或前臺線程(默認是False,前臺線程)。若是是後臺線程,主線程執行過程當中,後臺線程也在進行,主線程執行完畢後,後臺線程不論成功與否,均中止。若是是前臺線程,主線程執行過程當中,前臺線程也在進行,主線程執行完畢後,等待前臺線程執行完成後,程序才中止 |
ident | 獲取線程的標識符。線程標識符是一個非零整數,只有在調用了start()方法以後該屬性纔有效,不然它只返回None |
is_alive() | 判斷線程是不是激活的(alive)。從調用start()方法啓動線程,到run()方法執行完畢或遇到未處理異常而中斷這段時間內,線程是激活的 |
isDaemon()方法和daemon屬性 | 是否爲守護線程 |
join([timeout]) | 調用該方法將會使主調線程堵塞,直到被調用線程運行結束或超時。參數timeout是一個數值類型,表示超時時間,若是未提供該參數,那麼主調線程將一直堵塞到被調線程結束 |
import time
def single_thread():
print("這個單線程執行:%s"%time.time())
time.sleep(1)
def main():
for _ in range(5):
single_thread()
if __name__ == "__main__":
main()
複製代碼
import time
import threading
def single_thread():
print("這個單線程執行:%s"%time.time())
time.sleep(1)
def main():
for _ in range(5):
t = threading.Thread(target= single_thread)
t.start()
if __name__ == "__main__":
main()
複製代碼
執行結果:
test_code$ python3 001_single_thread.py
這個單線程執行:1563089407.2469919
這個單線程執行:1563089408.248269
這個單線程執行:1563089409.2495542
這個單線程執行:1563089410.2508354
這個單線程執行:1563089411.2516193
test_code$ python3 002_mulit_thread.py
這個多線程執行:1563089416.1928792
這個多線程執行:1563089416.1931264
這個多線程執行:1563089416.1932962
這個多線程執行:1563089416.1934686
這個多線程執行:1563089416.1936424
複製代碼
咱們剛看了單線程執行和兩個線程的執行效果,讓我回想起GIL裏講到的,在I/0密集操做程序中可使用多線程,這裏的耗時操做使用了time.sleep(1)來模仿了.接下來讓咱們學習更多的關於threading模塊的知識...
使用多線程併發操做,花費時間要短不少 當調用start(),纔會真正的建立線程,而且開始執行
#coding=utf-8
import threading
from time import sleep,ctime
def sing():
for i in range(3):
print("正在唱歌...%d"%i)
sleep(1)
def dance():
for i in range(3):
print("正在跳舞...%d"%i)
sleep(1)
if __name__ == '__main__':
print('---開始---:%s'%ctime())
t1 = threading.Thread(target=sing)
t2 = threading.Thread(target=dance)
t1.start()
t2.start()
#sleep(5) # 屏蔽此行代碼,試試看,程序是否會立馬結束?
print('---結束---:%s'%ctime())
複製代碼
import threading
from time import sleep,ctime
def sing():
for i in range(2):
sleep(1)
print("sing_ending...")
def dance():
for i in range(3):
sleep(1)
if __name__ == "__main__":
print("----開始----:%s"%ctime())
t1 = threading.Thread(target=sing)
t2 = threading.Thread(target=dance)
t1.start()
t2.start()
while True:
length = len(threading.enumerate())
print("當前運行的線程數量爲:%d"%length)
if length <= 1:
break
sleep(1)
複製代碼
運行結果:
----開始----:Sun Jul 14 17:11:24 2019
當前運行的線程數量爲:3
當前運行的線程數量爲:3
當前運行的線程數量爲:3
sing_ending...
當前運行的線程數量爲:2
當前運行的線程數量爲:1
test_code$
複製代碼
使用的都是在threading.Thread()實例化時,給裏面傳入對應的參數 threading.Thread(self, group=None, target=None, name=None, args=(),kwargs=None, *, daemon=None)
還有一種建立線程的方式是繼承threading.Thread類,重寫run方法,咱們來嘗試第二種方式
import threading
import time
class MyThread(threading.Thread):
def run(self):
print("I`m thread %s" % self.name)
if __name__ == '__main__':
t = MyThread()
t.start()
複製代碼
執行結果:
I`m thread Thread-1
複製代碼
總結
import threading
import time
class MyThread(threading.Thread):
def run(self):
for i in range(2):
time.sleep(0.5)
print("I`m %s %s" % (self.name, i))
def main():
for i in range(5):
t = MyThread()
t.start()
if __name__ == '__main__':
main()
複製代碼
執行結果:
I`m Thread-2 0
I`m Thread-3 0
I`m Thread-1 0
I`m Thread-4 0
I`m Thread-5 0
I`m Thread-2 1
I`m Thread-3 1
I`m Thread-4 1
I`m Thread-1 1
I`m Thread-5 1
複製代碼
總結
import threading
import time
num = 100
def count_test1():
global num
for i in range(10):
num += 1
print("count_test1-->num:%s"%num)
def count_test2():
global num
for i in range(5):
num += 1
print("count_test2-->num:%s"%num)
print("最原始的num:%s"%num)
t1 = threading.Thread(target=count_test1)
t1.start()
time.sleep(2) #讓t1執行完成
t2 = threading.Thread(target=count_test2)
t2.start()
複製代碼
執行結果:
最原始的num:100
count_test1-->num:110
count_test2-->num:115
複製代碼
import threading
import time
def count_test1(num_list):
num_list.append(10000)
print("count_test1-->num:%s"%num_list)
def count_test2(num_list):
print("count_test2-->num:%s"%num_list)
num_list = [11, 22, 33, 44]
t1 = threading.Thread(target=count_test1, args=(num_list,))
t1.start()
time.sleep(1) #讓t1執行完成
t2 = threading.Thread(target=count_test2, args=(num_list,))
t2.start()
複製代碼
執行結果:
count_test1-->num:[11, 22, 33, 44, 10000]
count_test2-->num:[11, 22, 33, 44, 10000]
複製代碼
總結
兩個線程(t1,t2)對同一個全局變量(global_num)進行修改,正常狀況下,t1對global_num加10,而後t2對global_num加10,最終global_num爲20.
But,在多線程中,存在這種狀況,t1獲取到global_num,此時系統將t1設置爲"sleep"狀態,這時t2獲取到global_num,對global_num進行加1,完成後,系統將t2設置爲"sleep"狀態,將t1設置爲"running"狀態,此時t1拿到的global_num是t2修改前的值,這時進行修改就會和t2修改重複.
測試1(循環數爲100)
import threading
import time
num = 0
def count_test1():
global num
for i in range(100):
num += 1
print("count_test1-->num:%s"%num)
def count_test2():
global num
for i in range(100):
num += 1
print("count_test2-->num:%s"%num)
t1 = threading.Thread(target=count_test1)
t2 = threading.Thread(target=count_test2)
t1.start()
t2.start()
t1.join()
t2.join()
print("最終的num:%s"%num)
複製代碼
測試結果:
count_test1-->num:100
count_test2-->num:200
最終的num:200
複製代碼
測試2(循環數爲100000)
import threading
import time
num = 0
def count_test1():
global num
for i in range(100000):
num += 1
print("count_test1-->num:%s"%num)
def count_test2():
global num
for i in range(100000):
num += 1
print("count_test2-->num:%s"%num)
t1 = threading.Thread(target=count_test1)
t2 = threading.Thread(target=count_test2)
t1.start()
t2.start()
t1.join()
t2.join()
print("最終的num:%s"%num)
複製代碼
測試結果:
count_test1-->num:100000
count_test2-->num:153462
最終的num:153462
複製代碼
總結
使用互斥鎖(循環數爲100000)
import threading
import time
num = 0
def count_test1():
global num
for i in range(100000):
mutex.acquire()
num += 1
mutex.release()
print("count_test1-->num:%s"%num)
def count_test2():
global num
for i in range(100000):
mutex.acquire()
num += 1
mutex.release()
print("count_test2-->num:%s"%num)
mutex = threading.Lock()
t1 = threading.Thread(target=count_test1)
t2 = threading.Thread(target=count_test2)
t1.start()
t2.start()
t1.join()
t2.join()
print("最終的num:%s"%num)
複製代碼
執行結果:
count_test1-->num:188038
count_test2-->num:200000
最終的num:200000
複製代碼
上鎖釋放鎖的過程
當一個線程調用鎖的acquire()方法得到鎖時,鎖就進入「locked」狀態
每次只有一個線程能夠得到鎖,若是此時另外一個線程試圖得到這個鎖,該線程就會變爲"blocked"狀態,稱爲"阻塞",直到擁有鎖的線程調用鎖的release()方法釋放鎖以後,鎖進入"unlocked"狀態。
線程調度程序從處於同步阻塞狀態的線程中選擇一個來得到鎖,並使得該線程進入運行(running)狀態
總結
情侶吵架後,都在等待對方道歉,若是雙方一直等待對方先開口,那麼結果就悲劇了...
情侶吵架和死鎖有什麼聯繫呢?若是兩個線程共享全局變量,兩個線程分別佔有必定的資源而且咋等待對方的資源,就會形成死鎖問題
#coding=utf-8
import threading
import time
class MyThread1(threading.Thread):
def run(self):
# 對mutexA上鎖
mutexA.acquire()
# mutexA上鎖後,延時1秒,等待另外那個線程 把mutexB上鎖
print(self.name+'----do1---up----')
time.sleep(1)
# 此時會堵塞,由於這個mutexB已經被另外的線程搶先上鎖了
mutexB.acquire()
print(self.name+'----do1---down----')
mutexB.release()
# 對mutexA解鎖
mutexA.release()
class MyThread2(threading.Thread):
def run(self):
# 對mutexB上鎖
mutexB.acquire()
# mutexB上鎖後,延時1秒,等待另外那個線程 把mutexA上鎖
print(self.name+'----do2---up----')
time.sleep(1)
# 此時會堵塞,由於這個mutexA已經被另外的線程搶先上鎖了
mutexA.acquire()
print(self.name+'----do2---down----')
mutexA.release()
# 對mutexB解鎖
mutexB.release()
mutexA = threading.Lock()
mutexB = threading.Lock()
if __name__ == '__main__':
t1 = MyThread1()
t2 = MyThread2()
t1.start()
t2.start()
複製代碼
執行結果
程序會卡住: 按唱、跳、Rap鍵+c退出
複製代碼
總結