天天看點

python3網絡程式設計之多線程

多線程類似于同時執行多個不同程式,多線程運作有如下優點:

  • 使用線程可以把占據長時間的程式中的任務放到背景去處理。
  • 使用者界面可以更加吸引人,這樣比如使用者點選了一個按鈕去觸發某些事件的處理,可以彈出一個進度條來顯示處理的進度
  • 程式的運作速度可能加快
  • 在一些等待的任務實作上如使用者輸入、檔案讀寫和網絡收發資料等,線程就比較有用了。在這種情況下我們可以釋放一些珍貴的資源如記憶體占用等等。

線程在執行過程中與程序還是有差別的。每個獨立的線程有一個程式運作的入口、順序執行序列和程式的出口。但是線程不能夠獨立執行,必須依存在應用程式中,由應用程式提供多個線程執行控制。

每個線程都有他自己的一組CPU寄存器,稱為線程的上下文,該上下文反映了線程上次運作該線程的CPU寄存器的狀态。

指令指針和堆棧指針寄存器是線程上下文中兩個最重要的寄存器,線程總是在程序得到上下文中運作的,這些位址都用于标志擁有線程的程序位址空間中的記憶體。

  • 線程可以被搶占(中斷)。
  • 在其他線程正在運作時,線程可以暫時擱置(也稱為睡眠) -- 這就是線程的退讓。

線程可以分為:

  • 核心線程:由作業系統核心建立和撤銷。
  • 使用者線程:不需要核心支援而在使用者程式中實作的線程。

Python3 線程中常用的兩個子產品為:

  • _thread
  • threading(推薦使用)

thread 子產品已被廢棄。使用者可以使用 threading 子產品代替。是以,在 Python3 中不能再使用"thread" 子產品。為了相容性,Python3 将 thread 重命名為 "_thread"。 

在這裡我們主要講解的是threading子產品。

_thread 提供了低級别的、原始的線程以及一個簡單的鎖,它相比于 threading 子產品的功能還是比較有限的。

threading 子產品除了包含 _thread 子產品中的所有方法外,還提供的其他方法:

  • threading.currentThread(): 傳回目前的線程變量。
  • threading.enumerate(): 傳回一個包含正在運作的線程的list。正在運作指線程啟動後、結束前,不包括啟動前和終止後的線程。
  • threading.activeCount(): 傳回正在運作的線程數量,與len(threading.enumerate())有相同的結果。

除了使用方法外,線程子產品同樣提供了Thread類來處理線程,Thread類提供了以下方法:

  • run(): 用以表示線程活動的方法。
  • start():啟動線程活動。 
  • join([time]): 等待至線程中止。這阻塞調用線程直至線程的join() 方法被調用中止-正常退出或者抛出未處理的異常-或者是可選的逾時發生。
  • isAlive(): 傳回線程是否活動的。
  • getName(): 傳回線程名。
  • setName(): 設定線程名。

在我們了解多線程之前,我們先來看一下單線程。

參考連結:http://www.cnblogs.com/fnng/p/3670789.html

單線程:

假設在很多年前,我想聽音樂和看電影一定要先排一下順序的。

from time import ctime,sleep

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

def move():
    for i in range(2):
        print("I was at the movies. %s" % ctime())
        sleep(5)

if __name__ == '__main__':
    music()
    move()
    print("all over %s" % ctime())      

我們先聽了一首音樂,通過for循環來控制音樂的播放了兩次,每一首音樂需要播放1秒鐘,sleep()來控制音樂播放的時長。接着又看了一場電影,每一場電影需要5秒鐘,我們通過for循環看了兩遍。聽完歌和看完電影以後,通過print("all over %s" % ctime())看了一下目前時間,準備結束這個娛樂活動。

運作結果:

I was listening to music. Tue Aug  1 17:08:15 2017
I was listening to music. Tue Aug  1 17:08:16 2017
I was at the movies. Tue Aug  1 17:08:17 2017
I was at the movies. Tue Aug  1 17:08:22 2017
all over Tue Aug  1 17:08:27 2017      

 如果我們把music()和move()看作是音樂播放器和視訊播放器的話,我們要播放什麼歌曲或者電影就應該由我們自己來決定。是以,就把以上代碼修改為:

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 move(func):
    for i in range(2):
        print("I was at the %s. %s" % (func, ctime()))
        sleep(5)

if __name__ == '__main__':
    music('十年')
    move('戰狼')
    print("all over %s" % ctime())      

這樣我們就對music()和move()進行了傳參處理。

運作結果:

I was listening to 十年. Tue Aug  1 17:17:22 2017
I was listening to 十年. Tue Aug  1 17:17:23 2017
I was at the 戰狼. Tue Aug  1 17:17:24 2017
I was at the 戰狼. Tue Aug  1 17:17:29 2017
all over Tue Aug  1 17:17:34 2017      

多線程:

跟着時代的發展,科技的進步,由于我們程式猿的幸苦工作,CPU也就越來越快了,我們就可以同時幹多件事情,是以我們一邊coding一邊聽歌的時代到來。

我們繼續對于以上的例子進行改變,引入threadring來同時播放音樂和視訊:

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 move(func):
    for i in range(2):
        print("I was at the %s. %s" % (func, ctime()))
        sleep(5)

# 建立了threads數組 把建立好的線程t1裝到threads數組中。
threads = []
# 建立線程t1,使用threading.Thread()方法,在這個方法中調用music方法target=music,args方法對music進行傳參。
t1 = threading.Thread(target=music, args=('十年',))
# 把建立好的線程t1裝到threads數組中
threads.append(t1)
# 接着以同樣的方式建立線程t2,并把t2也裝到threads數組。
t2 = threading.Thread(target=move, args=('戰狼',))
threads.append(t2)

if __name__ == '__main__':
    for t in threads:
        t.setDaemon(True)
        t.start()
    print("all over %s" % ctime())      

setDaemon()

setDaemon(True)将線程聲明為守護線程,必須在start() 方法調用之前設定,如果不設定為守護線程程式會被無限挂起。子線程啟動後,父線程也繼續執行下去,當父線程執行完最後一條語句print("all over %s" % ctime())後,沒有等待子線程,直接就退出了,同時子線程也一同結束。

 start()

開始線程活動。

運作結果:

I was listening to 十年. Tue Aug  1 17:31:29 2017
I was at the 戰狼. Tue Aug  1 17:31:29 2017
all over Tue Aug  1 17:31:29 2017      

從執行結果來看,子線程(muisc 、move )和主線程(print("all over %s" % ctime()))都是同一時間啟動,但由于主線程執行完結束,是以導緻子線程也終止。 

繼續調整程式:

...
if __name__ == '__main__':
    for t in threads:
        t.setDaemon(True)
        t.start()

    t.join()

    print("all over %s" % ctime())      

我們隻對上面的程式加了個join()方法,用于等待線程終止。join()的作用是,在子線程完成運作之前,這個子線程的父線程将一直被阻塞。

PS: join()方法的位置是在for循環外的,也就是說必須等待for循環裡的兩個程序都結束後,才去執行主程序。

運作結果:

I was listening to 十年. Tue Aug  1 20:24:03 2017
I was at the 戰狼. Tue Aug  1 20:24:03 2017
I was listening to 十年. Tue Aug  1 20:24:04 2017
I was at the 戰狼. Tue Aug  1 20:24:08 2017
all over Tue Aug  1 20:24:13 2017      

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

開始時間24分03秒,直到調用主程序為24分13秒,總耗時為10秒。從單線程時減少了2秒,我們可以把music的sleep()的時間調整為4秒。

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

...      

運作結果:

I was listening to 十年. Tue Aug  1 20:34:59 2017
I was at the 戰狼. Tue Aug  1 20:34:59 2017
I was listening to 十年. Tue Aug  1 20:35:03 2017
I was at the 戰狼. Tue Aug  1 20:35:04 2017
all over Tue Aug  1 20:35:09 2017      

子線程啟動34分59秒,主線程運作35分09秒。

雖然music每首歌曲從1秒延長到了4秒 ,但通過多程線的方式運作腳本,總的時間沒變化。

 class threading.Thread()說明:

 class threading.Thread(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)

 這個構造函數通常會用一些關鍵字參數,下面我們了解下這些關鍵字:

        group :這個變量作為保留變量,是為了以後擴充用的,暫時可以不用考慮。

        target: 是通過run()方法調用的可調用對象。預設為無,這意味着什麼都不做。

        name:線程的名字。預設情況下,一個唯一的名稱是”thread-n”,的形式,其中n是一個小的十進制數。

        args:元組參數,為target所調用的。

        kwargs:關鍵字參數的字典,為target所調用的。

        daemon: 設定daemon是否daemon 如果沒有顯示設定,daemon的屬性時從目前線程繼承。

        如果子類重寫此構造函數,它必須確定在做别的事情之前調用基類的構造函數(thread.__init__()。

線程有兩種調用方式:

1.直接調用

import threading
import time

def sayhi(num): #定義每個線程要運作的函數

    print("running on number:%s" %num)

    time.sleep(3)

if __name__ == '__main__':

    t1 = threading.Thread(target=sayhi,args=(1,)) #生成一個線程執行個體
    t2 = threading.Thread(target=sayhi,args=(2,)) #生成另一個線程執行個體

    t1.start() #啟動線程
    t2.start() #啟動另一個線程

    print(t1.getName()) #擷取線程名
    print(t2.getName())      

2.繼承式調用

import threading
import time

class MyThread(threading.Thread):
    def __init__(self,num):
        threading.Thread.__init__(self)
        self.num = num

    def run(self):#定義每個線程要運作的函數

        print("running on number:%s" % self.num)

        time.sleep(3)

if __name__ == '__main__':

    t1 = MyThread(1)
    t2 = MyThread(2)
    t1.start()
    t2.start()      

轉載于:https://www.cnblogs.com/Bigtre/p/7269468.html