亚洲国产日韩欧美一区二区三区,精品亚洲国产成人av在线,国产99视频精品免视看7,99国产精品久久久久久久成人热,欧美日韩亚洲国产综合乱

多線程編程

其實創(chuàng)建線程之后,線程并不是始終保持一個狀態(tài)的,其狀態(tài)大概如下:

New 創(chuàng)建

Runnable 就緒。等待調(diào)度

Running 運行

Blocked 阻塞。阻塞可能在 Wait Locked Sleeping

Dead 消亡

線程有著不同的狀態(tài),也有不同的類型。大致可分為:

主線程

子線程

守護線程(后臺線程)

前臺線程

簡單了解完這些之后,我們開始看看具體的代碼使用了。

1、線程的創(chuàng)建

Python 提供兩個模塊進行多線程的操作,分別是 thread 和 threading

前者是比較低級的模塊,用于更底層的操作,一般應用級別的開發(fā)不常用。

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import time
import threading
class MyThread(threading.Thread):
    def run(self):
        for i in range(5):
            print('thread {}, @number: {}'.format(self.name, i))
            time.sleep(1)
def main():
    print("Start main threading")
    # 創(chuàng)建三個線程
    threads = [MyThread() for i in range(3)]
    # 啟動三個線程
    for t in threads:
        t.start()
    print("End Main threading")
if __name__ == '__main__':
    main()

運行結果:

Start main threading
thread Thread-1, @number: 0
thread Thread-2, @number: 0
thread Thread-3, @number: 0
End Main threading
thread Thread-2, @number: 1
thread Thread-1, @number: 1
thread Thread-3, @number: 1
thread Thread-1, @number: 2
thread Thread-3, @number: 2
thread Thread-2, @number: 2
thread Thread-2, @number: 3
thread Thread-3, @number: 3
thread Thread-1, @number: 3
thread Thread-3, @number: 4
thread Thread-2, @number: 4
thread Thread-1, @number: 4

注意喔,這里不同的環(huán)境輸出的結果肯定是不一樣的。

2、線程合并(join方法)

上面的示例打印出來的結果來看,主線程結束后,子線程還在運行。那么我們需要主線程要等待子線程運行完后,再退出,要怎么辦呢?

這時候,就需要用到 join 方法了。

在上面的例子,新增一段代碼,具體如下:

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import time
import threading
class MyThread(threading.Thread):
    def run(self):
        for i in range(5):
            print('thread {}, @number: {}'.format(self.name, i))
            time.sleep(1)
def main():
    print("Start main threading")
    # 創(chuàng)建三個線程
    threads = [MyThread() for i in range(3)]
    # 啟動三個線程
    for t in threads:
        t.start()
    # 一次讓新創(chuàng)建的線程執(zhí)行 join
    for t in threads:
        t.join()
    print("End Main threading")
if __name__ == '__main__':
    main()

從打印的結果,可以清楚看到,相比上面示例打印出來的結果,主線程是在等待子線程運行結束后才結束的。

Start main threading
thread Thread-1, @number: 0
thread Thread-2, @number: 0
thread Thread-3, @number: 0
thread Thread-1, @number: 1
thread Thread-3, @number: 1
thread Thread-2, @number: 1
thread Thread-2, @number: 2
thread Thread-1, @number: 2
thread Thread-3, @number: 2
thread Thread-2, @number: 3
thread Thread-1, @number: 3
thread Thread-3, @number: 3
thread Thread-3, @number: 4
thread Thread-2, @number: 4
thread Thread-1, @number: 4
End Main threading

3、線程同步與互斥鎖

使用線程加載獲取數(shù)據(jù),通常都會造成數(shù)據(jù)不同步的情況。當然,這時候我們可以給資源進行加鎖,也就是訪問資源的線程需要獲得鎖才能訪問。

其中 threading 模塊給我們提供了一個 Lock 功能。

lock = threading.Lock()

在線程中獲取鎖

lock.acquire()

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

lock.release()

當然為了支持在同一線程中多次請求同一資源,Python 提供了可重入鎖(RLock)。RLock 內(nèi)部維護著一個 Lock 和一個 counter 變量,counter 記錄了 acquire 的次數(shù),從而使得資源可以被多次 require。直到一個線程所有的 acquire 都被 release,其他的線程才能獲得資源。

那么怎么創(chuàng)建重入鎖呢?也是一句代碼的事情:

r_lock = threading.RLock()

4、Condition 條件變量

實用鎖可以達到線程同步,但是在更復雜的環(huán)境,需要針對鎖進行一些條件判斷。Python 提供了 Condition 對象。使用 Condition 對象可以在某些事件觸發(fā)或者達到特定的條件后才處理數(shù)據(jù),Condition 除了具有 Lock 對象的 acquire 方法和 release 方法外,還提供了 wait 和 notify 方法。線程首先 acquire 一個條件變量鎖。如果條件不足,則該線程 wait,如果滿足就執(zhí)行線程,甚至可以 notify 其他線程。其他處于 wait 狀態(tài)的線程接到通知后會重新判斷條件。

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

35192f0e58595b25a0c422efd13ef05.png

該模式常用于生產(chǎn)者消費者模式,具體看看下面在線購物買家和賣家的示例:

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import threading, time
class Consumer(threading.Thread):
    def __init__(self, cond, name):
        # 初始化
        super(Consumer, self).__init__()
        self.cond = cond
        self.name = name
    def run(self):
        # 確保先運行Seeker中的方法
        time.sleep(1)
        self.cond.acquire()
        print(self.name + ': 我這兩件商品一起買,可以便宜點嗎')
        self.cond.notify()
        self.cond.wait()
        print(self.name + ': 我已經(jīng)提交訂單了,你修改下價格')
        self.cond.notify()
        self.cond.wait()
        print(self.name + ': 收到,我支付成功了')
        self.cond.notify()
        self.cond.release()
        print(self.name + ': 等待收貨')
class Producer(threading.Thread):
    def __init__(self, cond, name):
        super(Producer, self).__init__()
        self.cond = cond
        self.name = name
    def run(self):
        self.cond.acquire()
        # 釋放對瑣的占用,同時線程掛起在這里,直到被 notify 并重新占有瑣。
        self.cond.wait()
        print(self.name + ': 可以的,你提交訂單吧')
        self.cond.notify()
        self.cond.wait()
        print(self.name + ': 好了,已經(jīng)修改了')
        self.cond.notify()
        self.cond.wait()
        print(self.name + ': 嗯,收款成功,馬上給你發(fā)貨')
        self.cond.release()
        print(self.name + ': 發(fā)貨商品')
cond = threading.Condition()
consumer = Consumer(cond, '買家(兩點水)')
producer = Producer(cond, '賣家(三點水)')
consumer.start()
producer.start()

輸出的結果如下:

買家(兩點水): 我這兩件商品一起買,可以便宜點嗎
賣家(三點水): 可以的,你提交訂單吧
買家(兩點水): 我已經(jīng)提交訂單了,你修改下價格
賣家(三點水): 好了,已經(jīng)修改了
買家(兩點水): 收到,我支付成功了
買家(兩點水): 等待收貨
賣家(三點水): 嗯,收款成功,馬上給你發(fā)貨
賣家(三點水): 發(fā)貨商品

5、線程間通信

如果程序中有多個線程,這些線程避免不了需要相互通信的。那么我們怎樣在這些線程之間安全地交換信息或數(shù)據(jù)呢?

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

# -*- coding: UTF-8 -*-
from queue import Queue
from threading import Thread
isRead = True
def write(q):
    # 寫數(shù)據(jù)進程
    for value in ['兩點水', '三點水', '四點水']:
        print('寫進 Queue 的值為:{0}'.format(value))
        q.put(value)
def read(q):
    # 讀取數(shù)據(jù)進程
    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 對象實現(xiàn)了簡單的線程通信機制,它提供了設置信號,清楚信號,等待等用于實現(xiàn)線程間的通信。

設置信號

使用 Event 的 set() 方法可以設置 Event 對象內(nèi)部的信號標志為真。Event 對象提供了 isSe() 方法來判斷其內(nèi)部信號標志的狀態(tài)。當使用 event 對象的 set() 方法后,isSet() 方法返回真

清除信號

使用 Event 對象的 clear() 方法可以清除 Event 對象內(nèi)部的信號標志,即將其設為假,當使用 Event 的 clear 方法后,isSet() 方法返回假

等待

Event 對象 wait 的方法只有在內(nèi)部信號為真的時候才會很快的執(zhí)行并完成返回。當 Event 對象的內(nèi)部信號標志位假時,則 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對象內(nèi)部信號標志
        if event.isSet():
            event.clear()
            event.wait()
            print(self.getName())
        else:
            print(self.getName())
            # 設置Event對象內(nèi)部信號標志
            event.set()
# 生成Event對象
event = threading.Event()
# 設置Event對象內(nèi)部信號標志
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

6、后臺線程

默認情況下,主線程退出之后,即使子線程沒有 join。那么主線程結束后,子線程也依然會繼續(xù)執(zhí)行。如果希望主線程退出后,其子線程也退出而不再執(zhí)行,則需要設置子線程為后臺線程。Python 提供了 setDeamon 方法。

繼續(xù)學習