Python學習筆記(八)

原博客地址:http://www.cnblogs.com/fnng/archive/2013/12/24/3489321.html

在使用多線程之前,我們首頁要理解什麼是進程和線程。

什麼是進程?

計算機程序只不過是磁盤中可執行的,二進制(或其它類型)的數據。它們只有在被讀取到內存中,被操作系統調用的時候纔開始它們的生命期。進程(有時被稱爲重量級進程)是程序的一次執行。每個進程都有自己的地址空間,內存,數據棧以及其它記錄其運行軌跡的輔助數據。操作系統管理在其上運行的所有進程,併爲這些進程公平地分配時間。

什麼是線程?

線程(有時被稱爲輕量級進程)跟進程有些相似,不同的是,所有的線程運行在同一個進程中,共享相同的運行環境。我們可以想像成是在主進程或“主線程”中並行運行的“迷你進程”。

 

 7.2.1、單線程

  在單線程中順序執行兩個循環。一定要一個循環結束後,另一個才能開始。總時間是各個循環運行時間之和。 

onetherad.py

複製代碼
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 'start:', ctime()
    loop0() 
    loop1()
    print 'all end:', ctime() 
    
if __name__ == '__main__':
    main()
複製代碼

運行結果:

start loop 0 at: Mon Dec 23 09:59:44 2013
loop 0 done at: Mon Dec 23 09:59:48 2013
start loop 1 at: Mon Dec 23 09:59:48 2013
loop 1 done at: Mon Dec 23 09:59:50 2013
all end: Mon Dec 23 09:59:50 2013

  Python通過兩個標準庫threadthreading提供對線程的支持。thread提供了低級別的、原始的線程以及一個簡單的鎖。threading基於Java的線程模型設計。鎖(Lock)和條件變量(Condition)在Java中是對象的基本行爲(每一個對象都自帶了鎖和條件變量),而在Python中則是獨立的對象。

 

 

 7.2.1、thread模塊

 

mtsleep1.py

複製代碼
import thread 
from time import sleep, ctime 
loops = [4,2]
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:', ctime() 
    thread.start_new_thread(loop0, ()) 
    thread.start_new_thread(loop1, ()) 
    sleep(6)
    print 'all end:', ctime() 

if __name__ == '__main__':
    main() 
複製代碼

  start_new_thread()要求一定要有前兩個參數。所以,就算我們想要運行的函數不要參數,我們也要傳一個空的元組。 

  這個程序的輸出與之前的輸出大不相同,之前是運行了 6秒,而現在則是 秒,是最長的循環的運行時間與其它的代碼的時間總和。

運行結果:

複製代碼
start: Mon Dec 23 10:05:09 2013
start loop 0 at: Mon Dec 23 10:05:09 2013
start loop 1 at: Mon Dec 23 10:05:09 2013
loop 1 done at: Mon Dec 23 10:05:11 2013
loop 0 done at: Mon Dec 23 10:05:13 2013
all end: Mon Dec 23 10:05:15 2013
複製代碼

   睡眠 秒和 秒的代碼現在是併發執行的。這樣,就使得總的運行時間被縮短了。你可以看到,loop1 甚至在 loop0 前面就結束了。

  程序的一大不同之處就是多了一個“sleep(6)”的函數調用。如果我們沒有讓主線程停下來,那主線程就會運行下一條語句,顯示“all end”,然後就關閉運行着 loop0()和 loop1()的兩個線程並退出了。我們使用 秒是因爲我們已經知道,兩個線程(你知道,一個要 秒,一個要 秒)在主線程等待 秒後應該已經結束了。

  你也許在想,應該有什麼好的管理線程的方法,而不是在主線程裏做一個額外的延時 秒的操作。因爲這樣一來,我們的總的運行時間並不比單線程的版本來得少。而且,像這樣使用 sleep()函數做線程的同步操作是不可靠的。如果我們的循環的執行時間不能事先確定的話,那怎麼辦呢?這可能造成主線程過早或過晚退出。這就是鎖的用武之地了。

 

mtsleep2.py

複製代碼
#coding=utf-8
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 =[]
    #以loops數組創建列表,並賦值給nloops
    nloops = range(len(loops)) 
         
    for i in nloops:
        lock = thread.allocate_lock()
        #鎖定
        lock.acquire()
        #追加到locks[]數組中 
        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 end:', ctime() 

if __name__ == '__main__': 
    main()
複製代碼

thread.allocate_lock() 

  返回一個新的鎖定對象。

acquire() /release() 

  一個原始的鎖有兩種狀態,鎖定與解鎖,分別對應acquire()release() 方法。

range()

  range()函數來創建列表包含算術級數。

range(len(loops))理解:

複製代碼
>>> aa= "hello"

#長度計算
>>> len(aa)
5

#創建列表
>>> range(len(aa))
[0, 1, 2, 3, 4]

#循環輸出列表元素
>>> for a in range(len(aa)):
    print a

    
0
1
2
3
4
複製代碼

  我們先調用 thread.allocate_lock()函數創建一個鎖的列表,並分別調用各個鎖的 acquire()函數獲得鎖。獲得鎖表示“把鎖鎖上”。鎖上後,我們就把鎖放到鎖列表 locks 中。

  下一個循環創建線程,每個線程都用各自的循環號,睡眠時間和鎖爲參數去調用 loop()函數。爲什麼我們不在創建鎖的循環裏創建線程呢?有以下幾個原因:(1) 我們想到實現線程的同步,所以要讓“所有的馬同時衝出柵欄”。(2) 獲取鎖要花一些時間,如果你的線程退出得“太快”,可能會導致還沒有獲得鎖,線程就已經結束了的情況。

  在線程結束的時候,線程要自己去做解鎖操作。最後一個循環只是坐在那一直等(達到暫停主線程的目的),直到兩個鎖都被解鎖爲止才繼續運行。

mtsleep2.py運行結果:

複製代碼
starting at: Mon Dec 23 20:57:26 2013
start loop start loop0  1at:  at:Mon Dec 23 20:57:26 2013 
Mon Dec 23 20:57:26 2013
loop 1 done at: Mon Dec 23 20:57:28 2013
loop 0 done at: Mon Dec 23 20:57:30 2013
all end: Mon Dec 23 20:57:30 2013
複製代碼

 

 

 7.2.1、threading模塊

  我們應該避免使用thread模塊,原因是它不支持守護線程。當主線程退出時,所有的子線程不論它們是否還在工作,都會被強行退出。有時我們並不期望這種行爲,這時就引入了守護線程的概念。threading模塊則支持守護線程。

mtsleep3.py

複製代碼
#coding=utf-8
import threading 
from time import sleep, ctime 
 
loops = [4,2] 
 
def loop(nloop, nsec):
    print 'start loop', nloop, 'at:', ctime() 
    sleep(nsec) 
    print 'loop', nloop, 'done at:', ctime()
    
 
def main():
    print 'starting at:', ctime()
    threads = []
    nloops = range(len(loops)) 
        
    #創建線程
    for i in nloops:
        t = threading.Thread(target=loop,args=(i,loops[i]))
        threads.append(t)

    #開始線程
    for i in nloops:
        threads[i].start()

    #等待所有結束線程
    for i in nloops:
        threads[i].join()


    print 'all end:', ctime() 

if __name__ == '__main__': 
    main()
複製代碼

運行結果:

複製代碼
starting at: Mon Dec 23 22:58:55 2013
start loop 0 at: Mon Dec 23 22:58:55 2013
start loop 1 at: Mon Dec 23 22:58:55 2013
loop 1 done at: Mon Dec 23 22:58:57 2013
loop 0 done at: Mon Dec 23 22:58:59 2013
all end: Mon Dec 23 22:58:59 2013
複製代碼

start()

  開始線程活動

join()

  等待線程終止

 

  所有的線程都創建了之後,再一起調用 start()函數啓動,而不是創建一個啓動一個。而且,不用再管理一堆鎖(分配鎖,獲得鎖,釋放鎖,檢查鎖的狀態等),只要簡單地對每個線程調用 join()函數就可以了。

join()會等到線程結束,或者在給了 timeout 參數的時候,等到超時爲止。join()的另一個比較重要的方面是它可以完全不用調用。一旦線程啓動後,就會一直運行,直到線程的函數結束,退出爲止。

 

使用可調用的類

 mtsleep4.py

複製代碼
#coding=utf-8
import threading 
from time import sleep, ctime 
 
loops = [4,2] 

class ThreadFunc(object):

    def __init__(self,func,args,name=''):
        self.name=name
        self.func=func
        self.args=args

    def __call__(self):
        apply(self.func,self.args)

def loop(nloop,nsec):
    print "seart loop",nloop,'at:',ctime()
    sleep(nsec)
    print 'loop',nloop,'done at:',ctime()

def main():
    print 'starting at:',ctime()
    threads=[]
    nloops = range(len(loops))

    for i in nloops:
        #調用ThreadFunc實例化的對象,創建所有線程
        t = threading.Thread(
            target=ThreadFunc(loop,(i,loops[i]),loop.__name__)) 
        threads.append(t)
        
    #開始線程
    for i in nloops:
        threads[i].start()

    #等待所有結束線程
    for i in nloops:
        threads[i].join()

    print 'all end:', ctime() 

if __name__ == '__main__': 
    main()
複製代碼

 運行結果:

複製代碼
starting at: Tue Dec 24 16:39:16 2013
seart loop 0 at: Tue Dec 24 16:39:16 2013
seart loop 1 at: Tue Dec 24 16:39:16 2013
loop 1 done at: Tue Dec 24 16:39:18 2013
loop 0 done at: Tue Dec 24 16:39:20 2013
all end: Tue Dec 24 16:39:20 2013
複製代碼

創建新線程的時候,Thread 對象會調用我們的ThreadFunc 對象,這時會用到一個特殊函數__call__()。由於我們已經有了要用的參數,所以就不用再傳到 Thread()的構造函數中。由於我們有一個參數的元組,這時要在代碼中使用 apply()函數。

我們傳了一個可調用的類(的實例),而不是僅傳一個函數。

__init__()

方法在類的一個對象被建立時運行。這個方法可以用來對你的對象做一些初始化

apply()

apply(func [, args [, kwargs ]]) 函數用於當函數參數已經存在於一個元組或字典中時,間接地調用函數。args是一個包含將要提供給函數的按位置傳遞的參數的元組。如果省略了args,任何參數都不會被傳遞,kwargs是一個包含關鍵字參數的字典。

apply() 用法:

複製代碼
#不帶參數的方法
>>> def say():
    print 'say in'

>>> apply(say)
say in

#函數只帶元組的參數
>>> def say(a,b):
    print a,b

>>> apply(say,('hello','蟲師'))
hello 蟲師

#函數帶關鍵字參數
>>> def say(a=1,b=2):
    print a,b

    
>>> def haha(**kw):
    apply(say,(),kw)

    
>>> haha(a='a',b='b')
a b
複製代碼
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章