1. 程式人生 > >python 單執行緒和多執行緒

python 單執行緒和多執行緒

單執行緒, 在好些年前的MS-DOS時代,作業系統處理問題都是單任務的,我想做聽音樂和看電影兩件事兒,那麼一定要先排一下順序。

#coding=utf-8
import threading
from time import ctime,sleep

def music(func):
    for i in range(2):
        print "I was listening to %s. %s" %(func,ctime())
        sleep(1)

def movie(func):
    for i in range(2):
        print "I was at the %s! %s"
%(func,ctime()) sleep(5) if __name__ == '__main__': music(u'愛情買賣') movie(u'阿凡達') print "all over %s" %ctime()

一個任務只能等待上一個任務完成,才能執行。是阻塞的。

I was listening to 愛情買賣. Mon Feb 27 16:09:30 2017
I was listening to 愛情買賣. Mon Feb 27 16:09:31 2017
I was at the 阿凡達! Mon Feb 27 16:09:32 2017
I
was at the 阿凡達! Mon Feb 27 16:09:37 2017 all over Mon Feb 27 16:09:42 2017

多執行緒,python提供了兩個模組來實現多執行緒thread 和threading ,thread 有一些缺點,在threading 得到了彌補,為了不浪費你和時間,所以我們直接學習threading 就可以了。

#coding=utf-8
import threading
from time import ctime,sleep


def music(func):
    for i in range(2):
        print "I was listening to %s. %s"
%(func,ctime()) sleep(1) def movie(func): for i in range(2): print "I was at the %s! %s" %(func,ctime()) sleep(5) threads = [] t1 = threading.Thread(target=music,args=(u'愛情買賣',)) threads.append(t1) t2 = threading.Thread(target=movie,args=(u'阿凡達',)) threads.append(t2) if __name__ == '__main__': for t in threads: # setDaemon(True)將執行緒宣告為守護執行緒,必須在start() 方法呼叫之前設定,如果不設定為守護執行緒程式會被無限掛起。子執行緒啟動後,父執行緒也繼續執行下去,當父執行緒執行完最後一條語句print "all over %s" %ctime()後,沒有等待子執行緒,直接就退出了,同時子執行緒也一同結束。需要加一句t.join(),才會等待子執行緒。 t.setDaemon(True) t.start() # join()的作用是,在子執行緒完成執行之前,這個子執行緒的父執行緒將一直被阻塞。 t.join() print "all over %s" %ctime()
I was listening to 愛情買賣. Mon Feb 27 16:14:36 2017
I was at the 阿凡達! Mon Feb 27 16:14:36 2017
I was listening to 愛情買賣. Mon Feb 27 16:14:37 2017
I was at the 阿凡達! Mon Feb 27 16:14:41 2017
all over Mon Feb 27 16:14:46 2017

從執行結果可看到,music 和move 是同時啟動的。

開始時間36秒,直到呼叫主程序為46,總耗時為10秒。從單執行緒時減少了2秒。

這裡再聊聊,程序與執行緒的區別。

對於作業系統來說,一個任務就是一個程序。例如開啟瀏覽器,開啟word,開啟記事本等等,都是獨立的任務,它們各自為一個或者多個程序。這裡要注意的是,同一種任務開啟多個,分別屬於不同程序,例如chrome開啟多個標籤,實際上它建立了多個程序。

對於一個任務來說,它有很多子任務,例如播放器,既要解碼視訊、也要解碼音訊,所以在程序下存在多執行緒。在一個程序下一定存在一個執行緒,可以稱它為主執行緒。

作業系統建立程序時,會單獨為每一個程序分配各自的資源,程序與程序之間相互隔離。而程序內的執行緒,則共享了當前程序內的資源。可見,操作系統執行的粒度是執行緒,分配資源的粒度是程序,我們的多工作業系統,在單核CPU上是在各個執行緒上不斷切換而達到目的,而在多核CPU上則能同時執行多個執行緒任務。

概念上來說,多程序併發即執行多個獨立的程式,優勢在於併發處理的任務都由作業系統管理,不足之處在於程式與各程序之間的通訊和資料共享不方便。
多執行緒併發則由程式設計師管理併發處理的任務,這種併發方式可以方便地線上程間共享資料(前提是不能互斥)。

在linux中,每個程序都是由父程序提供的。每啟動一個子程序就從父程序克隆一份資料,但是程序之間的資料本身是不能共享的。
在一般情況下多個程序的記憶體資源是相互獨立的,而多執行緒可以共享同一個程序中的記憶體資源。多程序之間的通訊通過Queue()或Pipe()來實現。

GIL鎖( Global Interpreter Lock )
Python的執行緒雖然是真正的執行緒,但直譯器執行程式碼時,有一個GIL鎖:Global Interpreter Lock,任何Python執行緒執行前,必須先獲得GIL鎖,然後,每執行100條位元組碼,直譯器就自動釋放GIL鎖,讓別的執行緒有機會執行。這個GIL全域性鎖實際上把所有執行緒的執行程式碼都給上了鎖,所以,多執行緒在Python中只能交替執行,即使100個執行緒跑在100核CPU上,也只能用到1個核。

GIL是Python直譯器設計的歷史遺留問題,通常我們用的直譯器是官方實現的CPython,要真正利用多核,除非重寫一個不帶GIL的直譯器。

所以,在Python中,可以使用多執行緒,但不要指望能有效利用多核。如果一定要通過多執行緒利用多核,那隻能通過C擴充套件來實現,不過這樣就失去了Python簡單易用的特點。

不過,也不用過於擔心,Python雖然不能利用多執行緒實現多核任務,但可以通過多程序實現多核任務。多個Python程序有各自獨立的GIL鎖,互不影響。