Python直譯

高洛峰
高洛峰原創
2016-10-20 11:43:451218瀏覽

首先,說明多執行緒的應用場景:當python處理多個任務時,這些任務本質是異步的,需要有多個並發事務,各個事務的運行順序可以是不確定的、隨機的、不可預測的。計算密集型的任務可以依序執行分隔成的多個子任務,也可以用多執行緒的方式處理。但I/O密集型的任務就不好以單線程方式處理了,如果不用多線程,只能用一個或多個計時器來處理實作。

      下面說一下進程與執行緒:進程(有時叫重量級進程),是程式的一次執行,正如我們在centos中,ps -aux | grep something 的時候,總有一個他自身產生的進程,也就是這個grep進程,每個進程有自己的位址空間、記憶體、資料棧、及其他記錄其運行軌蹟的輔助數據,因此各個進程也不能直接共享訊息,只能用進程間通信(IPC)。

      執行緒(輕量級進程),與進程最大的差異是,所有的執行緒運行在同一個進程中,共享相同的運行環境,共享同一片資料空間。所以執行緒之間可以比進程之間更方便的共享資料以及相互通訊,並發執行完成事務。

      為了方便理解記憶進程與線程的關係,我們可以做一個類比:把cpu比作一個搬家公司,而這家搬家公司只有一輛車(進程)來供使用,開始,這家搬家公司很窮,只有一個員工(單線程),那麼,這個搬家公司一天,最多只能搬5家,後來,老闆賺到錢了,他沒買車,而是多雇了n個員工(多線程),這樣,每個員工會被安排每次只搬一家,然後就去休息,把車讓出來,讓其他人搬下一家,這看起來其實並沒有提高多少效率,反而增加了成本是吧,這是因為GIL(Global Interpreter Lock) 全域解釋器鎖,保證了線程安全(保證資料被安全讀取),即同時只能有一個線程在CPU上運行,這是python特有的機制,也就是說,即使你的運作環境有雙CPU,python虛擬機器也只會使用一個cpu,也就是說GIL 直接導致CPython 不能利用實體多核心的效能加速運算。具體的詳細解釋(歷史遺留問題,硬體發展太快)可以參考這篇部落格:

      http://blog.sina.com.cn/s/blog_64ecfc2f0102uzzf.html

  1   在作者我們不要使用thread模組,而是要使用threading模組,原因如下:

   1、當主執行緒退出時,所有其他執行緒沒有被清除就退出了,thread模組無法保護所有子執行緒的安全退出。即,thread         模組不支援守護程式。

   2、thread模組的屬性有可能會與threading出現衝突。

   3、低階的thread模組的同步原語很少(實際上只有一個,應該是sleep)。

一、thread模組 

 以下是兩個不使用GIL和使用GIL的範例程式碼:

 1.不使用GIL的程式碼範例:

from time import sleep,ctime
import thread

def loop0():
    print 'start loop 0 at: ',ctime()
    sleep(4)
    print 'loop 0 done at: ',ctime()
def loop1():
    print 'start loop 1 at: ',ctime()
    sleep(2)
    print 'loop 1 done at: ',ctime()
def main():
    print 'start at: ',ctime()
    thread.start_new_thread(loop0,())
    thread.start_new_thread(loop1,())
    sleep(6)
    print 'all loop is done, ' ,ctime()

if __name__=='__main__':
    main()
 

输出结果:

start at:  Thu Jan 28 10:46:27 2016
start loop 0 at:   Thu Jan 28 10:46:27 2016

start loop 1 at:   Thu Jan 28 10:46:27 2016
loop 1 done at:  Thu Jan 28 10:46:29 2016
loop 0 done at:  Thu Jan 28 10:46:31 2016
all loop is done,  Thu Jan 28 10:46:33 2016

由上述輸出可以看出,我們成功開啟了兩個執行緒,並且與主線程同步,在第2s時,loop1先完成,第4s時loop0完成,又過了2s,主線程完成結束。整個主線程經過了6s,loop0和loop1同步完成。

 

2、使用GIL的程式碼範例:

import thread
from time import sleep,ctime
loops = [4,2]
def loop(nloop,nsec,lock):
    print 'start loop',nloop,'at: ',ctime()
    sleep(nsec)
    print 'loop',nloop,'done at:',ctime()
    lock.release()
def main():
    print 'starting at:',ctime()
    locks = []
    nloops = range(len(loops))

    for i in nloops:
        lock = thread.allocate_lock()                          #创建锁的列表,存在locks中
        lock.acquire()                         
        locks.append(lock)                                      
    for i in nloops:
        thread.start_new_thread(loop,(i,loops[i],locks[i]))    #创建线程,参数为循环号,睡眠时间,锁
    for i in nloops:
        while locks[i].locked():                              #等待循环完成,解锁
            pass
    print 'all DONE at:',ctime()
if __name__ == '__main__':
    main()
 

以上输出如下:

starting at: Thu Jan 28 14:59:22 2016
start loop  0  at:   Thu Jan 28 14:59:22 2016

start loop  1  at:   Thu Jan 28 14:59:22 2016
loop 1 done at: Thu Jan 28 14:59:24 2016
loop 0 done at: Thu Jan 28 14:59:26 2016
all DONE at: Thu Jan 28 14:59:26 2016

歷時4秒,這樣效率提高,也比在主執行緒中用一個sleep()函數來計時更為合理。

 

二、threading模組

1、Thread類別

在thread類別中,可以用以下三種方法來建立執行緒:

(1)建立一個thread實例,傳給它一個函數)建立一個thread實例,傳給它一個可呼叫的類別物件

(3)從thread派生出一個子類,建立這個子類別的物件

方法(1)

__author__ = 'dell'
import threading
from time import sleep,ctime
def loop0():
    print 'start loop 0 at:',ctime()
    sleep(4)
    print 'loop 0 done at:',ctime()
def loop1():
    print 'start loop 1 at:',ctime()
    sleep(2)
    print 'loop 1 done at:',ctime()
def main():
    print 'starting at:',ctime()
    threads = []
    t1 = threading.Thread(target=loop0,args=())          #创建线程
    threads.append(t1)
    t2 = threading.Thread(target=loop1,args=())
    threads.append(t2)
    for t in threads:
        t.setDaemon(True)<span style="white-space:pre">    </span>      #开启守护线程(一定要在start()前调用)
        t.start()<span style="white-space:pre">        </span>      #开始线程执行
    for t in threads:<span style="white-space:pre">                    </span>
        t.join()<span style="white-space:pre">        </span>      #将程序挂起阻塞,直到线程结束,如果给出数值,则最多阻塞timeout秒

if __name__ == &#39;__main__&#39;:
    main()
    print &#39;All DONE at:&#39;,ctime()

在这里,就不用像thread模块那样要管理那么多锁(分配、获取、释放、检查等)了,同时我也减少了循环的代码,直接自己编号循环了,得到输出如下:
 

starting at: Thu Jan 28 16:38:14 2016
start loop 0 at: Thu Jan 28 16:38:14 2016
start loop 1 at: Thu Jan 28 16:38:14 2016
loop 1 done at: Thu Jan 28 16:38:16 2016
loop 0 done at: Thu Jan 28 16:38:18 2016
All DONE at: Thu Jan 28 16:38:18 2016

結果相同,但是從程式碼的邏輯來看,要清晰的多了。其他兩種在此就不貼程式碼了。實例化一個Thread與調用thread.start_new_thread直接最大的區別就是新的線程不會立即開始執行,也就是說,在threading模組的Thread類別中當我們實例化之後,再調用.start()函數後被統一執行,這使得我們的程式具有很好的同步特性。

下面是單線程與多線程的一個對比示例,分別以乘除完成兩組運算,從而看出多線程對效率的提高

from time import ctime,sleep
import threading

def multi():
    num1 = 1
    print &#39;start mutiple at:&#39;,ctime()
    for i in range(1,10):
       num1 = i*num1
       sleep(0.2)
    print &#39;mutiple finished at:&#39;,ctime()
    return num1
def divide():
    num2 = 100
    print &#39;start division at:&#39;,ctime()
    for i in range(1,10):
        num2 = num2/i
        sleep(0.4)
    print &#39;division finished at:&#39;,ctime()
    return num2
def main():
    print &#39;---->single Thread&#39;
    x1 = multi()
    x2 = divide()
    print &#39;The sum is &#39;,sum([x1,x2]),&#39;\nfinished singe thread&#39;,ctime()

    print &#39;----->Multi Thread&#39;
    threads = []
    t1 = threading.Thread(target=multi,args=())
    threads.append(t1)
    t2 = threading.Thread(target=divide,args=())
    threads.append(t2)
    for t in threads:
        t.setDaemon(True)
        t.start()
    for t in threads:
        t.join()

if __name__ == &#39;__main__&#39;:
    main()

结果如下:

 

---->single Thread

start mutiple at: Thu Jan 28 21:41:18 2016

mutiple finished at: Thu Jan 28 21:41:20 2016

start division at: Thu Jan 28 21:41:20 2016

division finished at: Thu Jan 28 21:41:24 2016

The sum is  362880 

finished singe thread Thu Jan 28 21:41:24 2016

----->Multi Thread

start mutiple at: Thu Jan 28 21:41:24 2016

start division at: Thu Jan 28 21:41:24 2016

mutiple finished at: Thu Jan 28 21:41:26 2016

division finished at: Thu Jan 28 21:41:27 2016

The sum is : 362880

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn