1. 程式人生 > >Python學習筆記二十二_多線程與多進程

Python學習筆記二十二_多線程與多進程

方法 很多 問題 quest 磁盤 結果 gil 解鎖 ems

一、什麽是線程&進程

1、進程 (Process)

  是資源的集合。其實就是程序(qq進程)。對於操作系統來說一個任務就是一個進程,例如打開瀏覽器就啟動了一個瀏覽器進程,打開word就啟動了一個word進程。

2、線程 (Thread)

  是程序裏面最小的執行單元。比如打開word,可以同時打字、拼寫檢查等,這些進程中的“子任務”就是線程。

3、線程是包含在一個進程裏面的,一個進程可以有多個線程

4、一個進程裏面默認有一個線程

5、主線程與子線程,一個程序默認有一個主線程,由主線程來啟動子線程

二、多線程

1、一個簡單的多線程,threading.Thread(target=方法)

import threading,time
def run():
    time.sleep(3)
    print(hello)
# for i in range(5): #串行,運行需要15s
#     run()
for i in range(5):#多線程,並行運行,3s
    t = threading.Thread(target=run)#實例化了一個線程
    t.start()

下面舉一個下載網頁的例子,列舉多線程的函數如何傳參,需要用args

import threading,time,requests
urls = {
    58:http://www.58.com/
, haozu:https://www.haozu.com/bj, }def down_html(file_name,url): req = requests.get(url).content #content返回二進制結果 open(file_name+.html,wb).write(req) #需要加上.html # 1、串行 # start_time = time.time() # for k,v in urls.items(): # down_html(k,v) # end_time = time.time() # run_time = end_time-start_time
# print(‘串行下載總共花了%s秒‘%run_time) #2.31秒 #2、並行 start_time = time.time() for k,v in urls.items(): t = threading.Thread(target=down_html,args=(k,v))#多線程的函數如果傳參的話,必須得用args t.start() end_time = time.time() run_time = end_time-start_time print(‘並行下載總共花了%s秒%run_time) #0.003秒

從這個下載網頁的例子中看到,並行下載的時間遠遠短於串行,但事實真的是這樣麽?

  我們看到並行運行時是先打印出時間,但是程序未運行結束。實際上打印的時間是主線程結束時間,主線程結束後子線程還未結束,所以程序未結束運行。所以0.003s這個時間是主線程運行的時間,而不是並行下載的時間。如果想看到並行下載的時間,就需要引入線程等待。

2、線程等待,t.join()

import threading,time,requests
urls = {58:http://www.58.com/,haozu:https://www.haozu.com/bj,}
def down_html(file_name,url):
    req = requests.get(url).content #content返回二進制結果
    open(file_name+.html,wb).write(req) #需要加上.html

start_time = time.time()
threads = []
for k,v in urls.items(): #2個線程
    t = threading.Thread(target=down_html,args=(k,v))
    t.start()
    threads.append(t)
#實際有3個線程,進程裏面默認有一個線程,這個線程叫做主線程
for t in threads:#主線程循環等待2個子線程執行結束
    t.join()#循環等待
end_time = time.time()
run_time = end_time-start_time
print(串行下載總共花了%s秒%run_time) #0.56秒

有了線程等待,主線程就會等到子線程全部執行結束再結束,這樣統計出的才是真正的並行下載時間。

這裏又有了一個新的問題,如果我們想看到每個線程運行的時間怎麽辦呢,需要在down_html函數中打印。但若需要獲得down_html函數返回值時,需要特殊處理,因為多線程調用函數時,函數的返回值是獲取不到的。

3、多線程調用函數時,如何獲取函數返回值

只能在函數外定義字典或者list來存儲返回值

import threading,time,requests
urls = {58:http://www.58.com/,haozu:https://www.haozu.com/bj,}
data = {}#多線程調用函數時,函數返回值獲取不到,只能在函數外定義字典或者list來存儲返回值
def down_html(file_name,url):
    start_time = time.time()
    req = requests.get(url).content
    open(file_name+.html,wb).write(req)
    end_time = time.time()
    run_time = end_time - start_time
    print(run_time, url)  # 打印每個進程的耗時
    data[url] = run_time  # 定義字典存儲返回值

start_time = time.time()
threads = []
for k,v in urls.items(): 
    t = threading.Thread(target=down_html,args=(k,v))
    t.start()
    threads.append(t)
for t in threads:#主線程循環等待2個子線程執行結束
    t.join()
end_time = time.time()
run_time = end_time-start_time
print(串行下載總共花了%s秒%run_time) #0.56秒

三、線程鎖

python2中在多個線程同時修改一個數據的時候,可能會把數據覆蓋,因此需要加線程鎖。但python3裏面不加鎖也無所謂,默認會自動幫你加鎖。

import threading
num = 1
lock = threading.Lock()#實例化一把鎖
#Python3默認會加鎖,python2需要加鎖,不然可能會覆蓋以前的數據
def run():
    global num
    lock.acquire()  # 加鎖
    num += 1
    lock.release()  # 解鎖
for i in range(10):
    t = threading.Thread(target=run)
    t.start()
print(num)

四、守護線程

五、多進程

1、如果這個函數中有返回值,怎麽獲取
子線程運行的函數,如果裏面有返回值的話,是不能獲取到的
只能在外面定義一個list或者字典來存每次處理的結果
電腦cpu有幾核,那麽只能同時運行幾個線程
但是python的多線程,只能利用一個cpu的核心
GIL全局解釋器鎖
2、鎖,在多個線程同時修改一個數據的時候,可能會把數據覆蓋,在python2裏面需要加鎖
python3裏面不加鎖也無所謂,默認會自動幫你加鎖

在python裏面,多線程被很多人詬病,為什麽呢,因為Python的解釋器使用了GIL的一個叫全局解釋器鎖,它不能利用多核CPU,只能運行在一個cpu上面,但是你在運行程序的時候,看起來好像還是在一起運行的,是因為操作系統輪流讓各個任務交替執行,任務1執行0.01秒,切換到任務2,任務2執行0.01秒,再切換到任務3,執行0.01秒……這樣反復執行下去。表面上看,每個任務都是交替執行的,但是,由於CPU的執行速度實在是太快了,我們感覺就像所有任務都在同時執行一樣。這個叫做上下文切換。
作業1:自己查一下
為什麽python的多線程不能利用多核cpu,但是在寫代碼的時候,多線程的確在並發,而且還比單線程快(博客)
3、守護線程
只要主線程結束,那麽子線程立即結束,不管子線程有沒有運行完成

多進程
多用於處理CPU密集型任務
例如排序、計算都是消耗cpu的
多線程
多用於處理IO密集型任務
頻繁寫入讀出,cpu負責調度,消耗的是磁盤空間

Python學習筆記二十二_多線程與多進程