2016/1/2 Python中的多線程(1):線程初探

---恢復內容開始---多線程

新年第一篇,繼續Python。併發


先來簡單介紹線程和進程。app

  計算機剛開始發展的時候,程序都是從頭至尾獨佔式地使用全部的內存和硬件資源,每一個計算機只能同時跑一個程序。後來引進了一些機制來改進這種調用方法,包括流水線,多進程。咱們開始併發執行程序,每一個程序交替地被處理器調用,再極高的頻率下,你會認爲這些程序是在同時執行的,這也就是併發技術。用操做系統來管理併發,將程序讀到內存中,而後被操做系統調用開始,它的生命週期就開始了。而每一個程序的執行,都是用進程的方式執行,每一個進程有本身的地址空間、內存、數據棧及別的什麼東西。對於任何一個以進程方式執行的程序,都好似獨佔了所有的硬件資源。每一個進程都有從地址0開始的虛擬內存地址。也就是說,進程是必定意義的抽象。而後操做系統管理全部的進程,給它們分配分時運行的時間。進程之間經過必定的進程間交互手段來通訊,不能直接共享信息。函數

  那什麼是線程?oop

  線程經常被稱爲輕量級進程,跟進程有些類似,不一樣的是全部的線程都運行在同一個進程中,共享一樣的運行環境,有一樣的地址空間、數據棧空間。能夠認爲在一個進程裏,有不少並行執行的線程。ui

  線程有開始,順序執行和結束三個部分。它有一個本身的指令指針,記錄本身運行到什麼地方。線程的運行可能被搶佔或者暫時掛起,讓其餘線程運行,這種方式叫作讓步。spa

  線程間因爲共享了一樣的數據空間,因此能夠很方便地共享數據和通信,可是,有一個問題是多個線程同時訪問同一片數據,因爲順序不一樣,可能數據結果會不一致,產生了所謂的競態條件。操作系統

  總的來講,線程是在一個程序裏設置的幾個併發運行的過程,讓幾件事情能夠同時執行。線程


Python中使用線程指針

 

  Python的代碼由Python虛擬機控制,能夠經過全局解釋器鎖GIL來控制,也能夠直接用一些模塊來實現咱們的需求。接下來主要來介紹thread和threading模塊。thread模塊通常不推薦使用,由於它在主線程退出時,其餘線程若沒有結束,尚未清除就退出了,而threading模塊確保全部的重要的子線程都退出後纔會結束進程。

  默認狀況下,Python對線程的支持是打開的。在交互模式下嘗試導入thread模塊沒有錯誤就表示可用。

>>> import thread
>>>

若是出現了導入錯誤,那麼應該從新編譯Python解釋器才能運行,這裏不做說明。


 

先來看一個沒有多線程支持例子:

這裏用了time模塊的sleep()函數,裏面輸入一個浮點的參數,表示睡眠的秒數,意味着程序將被掛起這段時間。

from time import sleep, ctime

def loop0():
    print 'start loop 0 at: %s' % ctime()
    sleep(4)
    print 'loop 0 done at: %s' % ctime()

def loop1():
    print 'start loop 1 at: %s' % ctime()
    sleep(2)
    print 'loop 1 done at: %s' % ctime()

def main():
    print 'starting at: %s' % ctime()
    loop0()
    loop1()
    print 'all Done at: %s' % ctime()

if __name__ == '__main__':
    main()
>>> 
starting at: Sat Jan 02 21:17:48 2016
start loop 0 at: Sat Jan 02 21:17:48 2016
loop 0 done at: Sat Jan 02 21:17:52 2016
start loop 1 at: Sat Jan 02 21:17:52 2016
loop 1 done at: Sat Jan 02 21:17:54 2016
all Done at: Sat Jan 02 21:17:54 2016

能夠看到,程序毫無疑問的順序執行了,可是,咱們用sleep()掛起的時間並無意義了。

因此,讓咱們看一下用了線程以後的方法:

import thread
from time import sleep, ctime

def loop0():
    print 'start loop 0 at: %s' % ctime()
    sleep(4)
    print 'loop 0 done at: %s' % ctime()

def loop1():
    print 'start loop 1 at: %s' % ctime()
    sleep(2)
    print 'loop 1 done at: %s' % ctime()

def main():
    print 'starting at: %s' % ctime()
    thread.start_new_thread(loop0,())
    thread.start_new_thread(loop1,())
    sleep(6)
    print 'all Done at: %s'% ctime()

if __name__ == '__main__':
    main()

結果會是這樣的

>>> 
starting at: Sat Jan 02 21:23:58 2016
start loop 0 at: Sat Jan 02 21:23:58 2016
start loop 1 at: Sat Jan 02 21:23:58 2016
loop 1 done at: Sat Jan 02 21:24:00 2016
loop 0 done at: Sat Jan 02 21:24:02 2016
all Done at: Sat Jan 02 21:24:04 2016

能夠看到,這一次loop1和loop0在程序開始後4秒就結束了,只是咱們多了一句sleep(6),讓整個程序最後仍是跑了6秒,加這一句,是防止主線程結束後子線程也就退出了,致使根本沒有執行完,可是這種方法實在是很愚蠢,咱們最後程序仍是跑了6秒才能結束,若是咱們有一次並不知道子進程何時結束,好比說從鍵盤讀到一條命令後結束,那麼該如何寫這樣的語句呢。接下來我會介紹這種方法,咱們先來看看thread這個模塊在此處幹了什麼。

  咱們調用了thread的一個方法start_new_thread(funciton, args kwargs=None),這個方法的做用是產生一個新線程,在新線程中用指定的參數和可選的kwargs來調用這個函數。這是一個很簡單的線程機制。                                                                                                                                                                                                                                                                                                                                                                                                                       

  接下來用鎖來杜絕在主線程中使用sleep()函數:

import thread
from time import ctime, sleep

loops = [4, 2]

def loop(nloop, nsec, lock):
    print 'start loop%s at: %s\n' % (nloop, ctime()),
    sleep(nsec)
    print 'loop%s done at: %s\n' % (nloop, ctime()),
    lock.release()

def main():
    print 'starting at: %s\n' % ctime(),
    locks = []
    nloops = range(len(loops))

    for i in nloops:
        lock = thread.allocate_lock()
        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: %s\n' %ctime(),

if __name__ == '__main__':
    main()

顯示結果是這樣的:

>>> 
starting at: Sat Jan 02 21:55:30 2016
start loop0 at: Sat Jan 02 21:55:30 2016
start loop1 at: Sat Jan 02 21:55:30 2016
loop1 done at: Sat Jan 02 21:55:32 2016
loop0 done at: Sat Jan 02 21:55:34 2016
all DONE at: Sat Jan 02 21:55:34 2016

你們能夠看到我用了一種很抽風的方式使用print語句,至於爲何不用基本的方法,各位能夠看一下用原來的方法輸出結果是怎樣的。

這裏面,咱們用了用了thread.allocate_lock()函數來建立一個鎖對象,而後將它存到一個鎖的列表裏去,每次都得調用acquire()函數來得到鎖,也就是把鎖鎖上,鎖上後,經過一個鎖的列表,在循環裏,每一個線程分配到本身的鎖,而後一塊兒執行。在線程結束的時候,咱們須要解鎖。

爲何不在建立鎖的過程當中建立進程呢?有兩個緣由,一個是咱們但願每一個線程都是同步開始的,要讓它們幾乎同時開始。另外一個是每次獲取鎖會花必定的時間,若是線程退出的太快,可能鎖尚未獲取,線程就結束了。

因此咱們須要分配鎖,得到鎖,釋放鎖,來實現進程的同步。

 

今天先寫到這裏,下一次再說明threading的使用,那時候,將不須要考慮這些鎖的問題。

相關文章
相關標籤/搜索