Python 學習之線程

線程是最小的執行單元,而進程由至少一個線程組成。如何調度進程和線程,完全由操作系統決定,程序自己不能決定什麼時候執行,執行多長時間。
在 Python 中我們要同時執行多個任務怎麼辦?
有兩種解決方案:

  • 啓動多個進程,每個進程雖然只有一個線程,但多個進程可以一塊執行多個任務。
  • 啓動一個進程,在一個進程內啓動多個線程,這樣,多個線程也可以一塊執行多個任務。
  • 啓動多個進程,每個進程再啓動多個線程,這樣同時執行的任務就更多了,當然這種模型更復雜,實際很少採用。
    總結一下就是,多任務的實現有 3 種方式:
  • 多進程模式;
  • 多線程模式;
  • 多進程+多線程模式。

多線程

Python 的標準庫提供了兩個模塊:_thread 和 threading,_thread 是低級模塊, threading 是高級模塊,對 _thread 進行了封裝。絕大多數情況下,我們只需要使用 threading 這個高級模塊。

import time, threading

# 新線程執行的代碼:
def loop():
    print('thread %s is running...' % threading.current_thread().name)
    n = 0
    while n < 5:
        n = n + 1
        print('thread %s >>> %s' % (threading.current_thread().name, n))
        time.sleep(1)
    print('thread %s ended.' % threading.current_thread().name)

print('thread %s is running...' % threading.current_thread().name)
t = threading.Thread(target=loop, name='LoopThread')
t.start()
t.join()
print('thread %s ended.' % threading.current_thread().name)

結果:

thread MainThread is running...
thread LoopThread is running...
thread LoopThread >>> 1
thread LoopThread >>> 2
thread LoopThread >>> 3
thread LoopThread >>> 4
thread LoopThread >>> 5
thread LoopThread ended.
thread MainThread ended.

Python 的 threading 模塊有個 current_thread() 函數,它永遠返回當前線程的實例。主線程實例的名字叫 MainThread ,子線程的名字在創建時指定,我們用 LoopThread 命名子線程。名字僅僅在打印時用來顯示,完全沒有其他意義,如果不起名字 Python 就自動給線程命名爲 Thread-1,Thread-2……

線程同步與互斥鎖

使用線程加載獲取數據,通常都會造成數據不同步的情況。當然,這時候我們可以給資源進行加鎖,也就是訪問資源的線程需要獲得鎖才能訪問。
其中 threading 模塊給我們提供了一個 Lock 功能。

lock = threading.Lock()

在線程中獲取鎖

lock.acquire()

使用完成後,我們肯定需要釋放鎖

lock.release()

Condition 條件變量

Python 提供了 Condition 對象。
使用 Condition 對象可以在某些事件觸發或者達到特定的條件後才處理數據,Condition 除了具有 Lock 對象的 acquire 方法和 release 方法外,還提供了 wait 和 notify 方法。

線程首先 acquire 一個條件變量鎖。如果條件不足,則該線程 wait,如果滿足就執行線程,甚至可以 notify 其他線程。其他處於 wait 狀態的線程接到通知後會重新判斷條件。

其中條件變量可以看成不同的線程先後 acquire 獲得鎖,如果不滿足條件,可以理解爲被扔到一個( Lock 或 RLock )的 waiting 池。直到其他線程 notify 之後再重新判斷條件。不斷的重複這一過程,從而解決複雜的同步問題。
image

線程間通信

從一個線程向另一個線程發送數據最安全的方式可能就是使用 queue 庫中的隊列了。創建一個被多個線程共享的 Queue 對象,這些線程通過使用 put() 和 get() 操作來向隊列中添加或者刪除元素。

# -*- coding: UTF-8 -*-
from queue import Queue
from threading import Thread

isRead = True


def write(q):
    # 寫數據進程
    for value in ['兩點水', '三點水', '四點水']:
        print('寫進 Queue 的值爲:{0}'.format(value))
        q.put(value)


def read(q):
    # 讀取數據進程
    while isRead:
        value = q.get(True)
        print('從 Queue 讀取的值爲:{0}'.format(value))


if __name__ == '__main__':
    q = Queue()
    t1 = Thread(target=write, args=(q,))
    t2 = Thread(target=read, args=(q,))
    t1.start()
    t2.start()

結果:

寫進 Queue 的值爲:兩點水
寫進 Queue 的值爲:三點水
從 Queue 讀取的值爲:兩點水
寫進 Queue 的值爲:四點水
從 Queue 讀取的值爲:三點水
從 Queue 讀取的值爲:四點水

Python 還提供了 Event 對象用於線程間通信,它是由線程設置的信號標誌,如果信號標誌位真,則其他線程等待直到信號接觸。

Event 對象實現了簡單的線程通信機制,它提供了設置信號,清楚信號,等待等用於實現線程間的通信。

  • 設置信號
    使用 Event 的 set() 方法可以設置 Event 對象內部的信號標誌爲真。Event 對象提供了 isSe() 方法來判斷其內部信號標誌的狀態。當使用 event 對象的 set() 方法後,isSet() 方法返回真

  • 清除信號
    使用 Event 對象的 clear() 方法可以清除 Event 對象內部的信號標誌,即將其設爲假,當使用 Event 的 clear 方法後,isSet() 方法返回假

  • 等待
    Event 對象 wait 的方法只有在內部信號爲真的時候纔會很快的執行並完成返回。當 Event 對象的內部信號標誌位假時,則 wait 方法一直等待到其爲真時才返回。

實例

# -*- coding: UTF-8 -*-

import threading


class mThread(threading.Thread):
    def __init__(self, threadname):
        threading.Thread.__init__(self, name=threadname)

    def run(self):
        # 使用全局Event對象
        global event
        # 判斷Event對象內部信號標誌
        if event.isSet():
            event.clear()
            event.wait()
            print(self.getName())
        else:
            print(self.getName())
            # 設置Event對象內部信號標誌
            event.set()

# 生成Event對象
event = threading.Event()
# 設置Event對象內部信號標誌
event.set()
t1 = []
for i in range(10):
    t = mThread(str(i))
    # 生成線程列表
    t1.append(t)

for i in t1:
    # 運行線程
    i.start()

結果:

1
0
3
2
5
4
7
6
9
8

小結

多線程編程,模型複雜,容易發生衝突,必須用鎖加以隔離,同時,又要小心死鎖的發生。

Python解釋器由於設計時有GIL全局鎖,導致了多線程無法利用多核。多線程的併發在Python中就是一個美麗的夢。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章