Python 多線程和鎖

Python 多線程和鎖

做者博客:http://zzir.cnpython

進程和線程

進程是執行中的計算機程序。每一個進程都擁有本身的地址空間、內存、數據棧及其它的輔助數據。操做系統管理着全部的進程,併爲這些進程合理分配時間。進程能夠經過派生新的進程來執行其它任務,不過每一個進程都擁有本身的內存和數據棧等,進程之間的數據交換採用 進程間通訊(IPC) 方式。多線程

線程在進程之下執行,一個進程下能夠運行多個線程,它們之間共享相同上下文。線程包括開始、執行順序和結束三部分。它有一個指針,用於記錄當前運行的上下文。當其它線程執行時,它能夠被搶佔(中斷)和臨時掛起(也稱睡眠) ——這種作法叫作 讓步(yielding)併發

一個進程中的各個線程與主進程共享同一片數據空間,與獨立進程相比,線程之間信息共享和通訊更加容易。線程通常以併發執行,正是因爲這種併發和數據共享機制,使多任務間的協做成爲可能。固然,這種共享也並非沒有風險的,若是多個線程訪問同一數據空間,因爲訪問順序不一樣,可能致使結果不一致,這種狀況一般稱爲競態條件(race condition),不過大多數線程庫都有同步原語,以容許線程管理器的控制執行和訪問;另外一個要注意的問題是,線程沒法給予公平執行時間,CPU 時間分配會傾向那些阻塞更少的函數。app

全局解釋器鎖(GIL)

Python 代碼執行由 Python 虛擬機 (又名解釋器主循環) 進行控制。Python 在設計時是這樣考慮的,在主循環中同時只能有一個控制線程在執行。對 Python 虛擬機的訪問由 全局解釋器(GIL) 控制,這個鎖用於,當有多個線程時保證同一時刻只能有一個線程在運行。dom

因爲 Python 的 GIL 的限制,多線程更適合 I/O 密集型應用( I/O 釋放了 GIL,能夠容許更多的併發),對於計算密集型應用,爲了實現更好的並行性,適合使用多進程,已便利用 CPU 的多核優點。Python 的多進程相關模塊:subprocess、multiprocessing、concurrent.futureside

threading 模塊

threading 是 Python 高級別的多線程模塊。函數

threading 模塊的函數

  • active_count() 當前活動的 Thread 對象個數
  • current_thread() 返回當前 Thread 對象
  • get_ident() 返回當前線程
  • enumerater() 返回當前活動 Thread 對象列表
  • main_thread() 返回主 Thread 對象
  • settrace(func) 爲全部線程設置一個 trace 函數
  • setprofile(func) 爲全部線程設置一個 profile 函數
  • stack_size([size]) 返回新建立線程棧大小;或爲後續建立的線程設定棧大小爲 size
  • TIMEOUT_MAX Lock.acquire(), RLock.acquire(), Condition.wait() 容許的最大值

threading 可用對象列表:ui

  • Thread 表示執行線程的對象
  • Lock 鎖原語對象
  • RLock 可重入鎖對象,使單一進程再次得到已持有的鎖(遞歸鎖)
  • Condition 條件變量對象,使得一個線程等待另外一個線程知足特定條件,好比改變狀態或某個值
  • Semaphore 爲線程間共享的有限資源提供一個"計數器",若是沒有可用資源會被阻塞
  • Event 條件變量的通用版本,任意數量的線程等待某個時間的發生,在改事件發生後全部線程被激活
  • Timer 與 Thread 相識,不過它要在運行前等待一段時間
  • Barrier 建立一個"阻礙",必須達到指定數量的線程後才能夠繼續

Thread 類

Thread 對象的屬性有:Thread.nameThread.identThread.daemon。詳見(The Python Standard Library)操作系統

Thread 對象方法:
Thread.start()Thread.run()Thread.join(timeout=None)Thread.getNameThread.setNameThread.is_alive()Thread.isDaemon()Thread.setDaemon()。詳見(The Python Standard Library)線程

使用 Thread 類,能夠有不少種方法來建立線程,這裏使用常見的兩種:

  • 建立 Thread 實例,傳給它一個函數。
  • 派生 Thread 子類,並建立子類的實例。

一個單線程栗子

#!/usr/bin/env python3

import threading
from random import randint
from time import sleep, ctime

def hi(n):
    sleep(n)
    print("ZzZzzz, sleep: ", n)    # 打印 Sleep 的秒數

def main():
    print("### Start at: ", ctime())

    for i in range(10):
        hi(randint(1,2))    # 調用十次,每次 Sleep 1秒或2秒

    print("### Done at: ", ctime())

if __name__ == '__main__':
    main()

運行結果:

### Start at:  Thu Sep  1 14:11:00 2016
ZzZzzz, sleep:  1
ZzZzzz, sleep:  2
ZzZzzz, sleep:  2
ZzZzzz, sleep:  2
ZzZzzz, sleep:  1
ZzZzzz, sleep:  1
ZzZzzz, sleep:  1
ZzZzzz, sleep:  1
ZzZzzz, sleep:  1
ZzZzzz, sleep:  2
### Done at:  Thu Sep  1 14:11:14 2016

一共是用了14秒。

多線程:建立 Thread 實例,傳給它一個函數

直接上代碼:

#!/usr/bin/env python3

import threading
from random import randint
from time import sleep, ctime

def hi(n):
    sleep(n)
    print("ZzZzzz, sleep: ", n)

def main():
    print("### Start at: ", ctime())
    threads = []

    for i in range(10):
        rands = randint(1,2)
        # 實例化每一個 Thread 對象,把函數和參數傳遞進去,返回 Thread 實例
        t = threading.Thread(target=hi, args=(rands,))
        threads.append(t)     # 分配線程

    for i in range(10):
        threads[i].start()    # 開始執行多線程

    for i in range(10):
        threads[i].join()     # (自旋鎖)等待線程結束或超時,而後再往下執行

    print("### Done at: ", ctime())

if __name__ == '__main__':
    main()

運行結果:

### Start at:  Thu Sep  1 14:18:00 2016
ZzZzzz, sleep:  1
ZzZzzz, sleep:  1
ZzZzzz, sleep:  1
ZzZzzz, sleep:  1
ZzZzzz, sleep:  1
ZzZzzz, sleep:  1
ZzZzzz, sleep:  2
ZzZzzz, sleep:  2
ZzZzzz, sleep:  2
ZzZzzz, sleep:  2
### Done at:  Thu Sep  1 14:18:02 2016

使用多線程,只用了2秒。

多線程:派生 Thread 子類,並建立子類的實例

#!/usr/bin/env python3

import threading
from random import randint
from time import sleep, ctime

class MyThread(threading.Thread):
 
    def __init__(self, func, args, times):
        super(MyThread, self).__init__()
        self.func = func
        self.args = args
        self.times = times
    
    def run(self):
        print("begin thread......", self.times)
        self.res = self.func(*self.args)
        print("end threads......", self.times)


def hi(n):
    sleep(n)
    print("ZzZzzz, sleep: ", n)

def main():
    print("### Start at: ", ctime())
    threads = []

    for i in range(10):
        rands = randint(1,2)
        t = MyThread(hi, (rands,), i+1)
        threads.append(t)

    for i in range(10):
        threads[i].start()

    for i in range(10):
        threads[i].join()

    print("### Done at: ", ctime())

if __name__ == '__main__':
    main()

執行結果:

### Start at:  Thu Sep  1 14:47:09 2016
begin thread...... 1
begin thread...... 2
begin thread...... 3
begin thread...... 4
begin thread...... 5
begin thread...... 6
begin thread...... 7
begin thread...... 8
begin thread...... 9
begin thread...... 10
ZzZzzz, sleep:  1
ZzZzzz, sleep:  1
end threads...... 1
end threads...... 4
ZzZzzz, sleep:  1
end threads...... 7
ZzZzzz, sleep:  1
end threads...... 3
ZzZzzz, sleep:  1
end threads...... 9
ZzZzzz, sleep:  2
end threads...... 2
ZzZzzz, sleep:  2
end threads...... 5
ZzZzzz, sleep:  2
ZzZzzz, sleep:  2
end threads...... 10
end threads...... 6
ZzZzzz, sleep:  2
end threads...... 8
### Done at:  Thu Sep  1 14:47:11 2016

這個栗子對 Thread 子類化,而不是對其實例化,使得定製線程對象更具靈活性,同時也簡化線程建立的調用過程。

線程鎖

當多線程爭奪鎖時,容許第一個得到鎖的線程進入臨街區,並執行代碼。全部以後到達的線程將被阻塞,直到第一個線程執行結束,退出臨街區,並釋放鎖。須要注意,那些阻塞的線程是沒有順序的。

舉個栗子:

#!/usr/bin/env python3

import threading
from random import randint
from time import sleep, ctime

L = threading.Lock() # 引入鎖

def hi(n):
    L.acquire()    # 加鎖
    for i in [1,2]:
        print(i)
        sleep(n)
        print("ZzZzzz, sleep: ", n)
    L.release()    # 釋放鎖

def main():
    print("### Start at: ", ctime())
    threads = []

    for i in range(10):
        rands = randint(1,2)
        t = threading.Thread(target=hi, args=(rands,))
        threads.append(t)

    for i in range(10):
        threads[i].start()

    for i in range(10):
        threads[i].join()

    print("### Done at: ", ctime())

if __name__ == '__main__':
    main()

運行上面的代碼,再將鎖的代碼註釋掉,對比下輸出。

相關文章
相關標籤/搜索