線程同步與併發

今天來學習線程同步與併發,咱們先來看一下線程之間的幾種通訊方式:html

1.線程之間的幾種通訊方式

  • Event:事件;編程

  • Critical Section:臨界區;安全

  • Semaphone:信號量;網絡

2.Event事件的使用

  • Event是事件處理的機制,全局定義了一個內置標誌Flag,若是Flag值爲 False,那麼當程序執行 event.wait方法時就會阻塞,若是Flag值爲True, 那麼event.wait 方法時便再也不阻塞;

咱們先來看看下面的實例:多線程

from threading import Thread, Event
import time

def teacher(event: Event):
    print('I am teacher , waiting for your homework')
    event.wait()
    print("I am teacher, already obtaining student's homework ")
    
def student(event: Event):
    finished_homework = []
    while len(finished_homework) < 10:
        time.sleep(1)
        print('I am student, finished one homework')
        finished_homework.append(1)
    else:
        print('student finish homework')
        event.set()
        
if __name__ == '__main__':
    event = Event()
    Thread(target=student, args=(event,)).start()
    Thread(target=teacher, args=(event,)).start()

這個實例首先從threading 模塊中導入了Thread和Event兩個類,而後定義了兩個方法teacher和student,在if __name__ == '__main__'中執行了這兩個方法。併發

按照正常通常的代碼執行順序,會先打印teacher方法中的第一個print,而後來到了event.wait()wait()方法的語法爲wait(self, timeout=None)timeout用於設置等待的時長,若是超過期長則再也不等待,直接向下執行,若是timeout沒有指定則一 直等待,等待的時候是阻塞的app

咱們能夠看到teacher方法中的event.wait()並無設置timeout,因此會阻塞,開始執行student方法,student方法裏面是一個while循環,須要打印10遍print中的內容,打印完畢後進入else,其中有一個event.set()方法,會將flag設置爲True,wait等待的線程就能夠向下執行;函數

因此剛剛teacher方法中等待的會繼續執行,咱們來看一下輸出結果:學習

I am teacher , waiting for your homework
I am student, finished one homework
I am student, finished one homework
I am student, finished one homework
I am student, finished one homework
I am student, finished one homework
I am student, finished one homework
I am student, finished one homework
I am student, finished one homework
I am student, finished one homework
I am student, finished one homework
student finish homework
I am teacher, already obtaining student's homework

event實例對象的對象方法介紹:fetch

  • wait(self, timeout=None):timeout爲設置等待的時長,若是超過期長(返回值爲False)則再也不等待,直接向下執行,若是timeout沒有指定則一 直等待,等待的時候是阻塞的沒有返回值,;

  • set():若是執行event.set(),將會設置flag爲True,那麼wait等待的線程就能夠向下執行;

  • clear():若是執行event.clear(),將會設置flag標記爲Flase, 那麼wait等待的線程將再次等待(阻塞);

  • is_set():判斷event的flag是否爲True,若是爲True的話wait等待的線程將向下執行;

3.線程鎖

  • Lock是Python中最底層的同步機制,直接由底層模塊 thread 實現,每一個lock對象只有兩種狀態,也就是上鎖和未上鎖;
  • 咱們能夠經過下面兩種方式建立一個Lock對象,注意新建立的 Lock 對象處於未上鎖的狀態:
thread.allocate_lock()
threading.Lock()
  • 鎖是解決臨界區資源的問題,保證每個線程訪問臨界資源的時候有所有的權利,一旦某個線程得到鎖, 其它試圖獲取鎖的線程將被阻塞;

  • locked()方法:用於判斷當前是否上鎖,若是上鎖,返回True,不然返回False;

  • acquire(blocking=True,timeout=-1):表示加鎖,默認True爲加鎖狀態(阻塞),False爲不阻塞,timeout爲設置時長;

  • release()方法:用於釋放鎖,在完成任務的時候釋放鎖,讓其餘的線程獲取到臨界資源,注意此時必須處於上鎖的狀態,若是試圖釋放一個unlocked 的鎖,將拋出異常 thread.error

咱們經過兩個實例來看一下線程鎖的做用:

實例1:

import time
from threading import Thread

homework_list = []

def student(number):
    while len(homework_list) < number:  # 知足條件則繼續執行下面的代碼
        time.sleep(0.001)   # 等待0.001秒
        homework_list.append(1)
    print(len(homework_list))
if __name__ == '__main__':
    for i in range(10):
        Thread(target=student, args=(10, )).start()  # 同時開啓10個線程
    time.sleep(3)   # 等待3秒
    print('完成做業的總數爲: {}'.format(len(homework_list)))

運行實例1,咱們發現最終輸出結果完成做業的總數爲:19,可是根據代碼邏輯來看,應該輸出10纔對,這是爲何呢?實際上是由於在多線程狀況下操做臨界資源,出現了臨界資源爭搶的問題,那如何解決這個問題呢,咱們來看實例2;

實例2:

import time
import threading
from threading import Thread, Lock

homework_list = []
lock = Lock()   # 全局阻塞鎖
def student(number):
    while True:
        lock.acquire()  # 必定要在獲取臨界資源以前加鎖
        if len(homework_list) >= number:  # 知足條件則跳出循環,不知足則繼續
            break
        time.sleep(0.001)  # 等待0.001秒
        homework_list.append(1)
        lock.release()   # 完成任務的時候釋放鎖,讓其餘的線程獲取到臨界資源
        print('current_thread={}, homework_list={}'.format(threading.current_thread().name, len(homework_list)))

if __name__ == '__main__':
    for i in range(10):
        Thread(target=student, name='student {}'.format(i), args=(10, )).start()  # 同時開啓10個線程
    time.sleep(3)    #  等待3秒
    print('完成做業的總數爲: {}'.format(len(homework_list)))

運行實例2發現最終輸出完成做業的總數爲: 10,是咱們想要的結果,實例1和實例2 不一樣之處在於實例2在獲取臨界資源以前加了鎖,也就是lock.acquire(),咱們知道一旦某個線程得到鎖, 其它試圖獲取鎖的線程將被阻塞,因此這裏不會再發生臨界資源爭搶的問題了;

4.鎖在with語句中的使用

  • 使用with語句加鎖with lock,會默認自動釋放鎖,不須要再使用release()方法來釋放鎖了,這樣能夠避免程序中忘記釋放鎖,更加方便;
import time
import threading
from threading import Thread, Lock
homework_list = []

# 全局阻塞鎖
lock = Lock()
def student(number):
    while True:
        with lock:
            if len(homework_list) >= number:
                break
            time.sleep(0.001)
            homework_list.append(1)

        print('current_thread={}, homework_list={}'.format(threading.current_thread().name, len(homework_list)))

if __name__ == '__main__':
    for i in range(10):
        Thread(target=student, name='student {}'.format(i), args=(1000, )).start()
    time.sleep(3)
    print('完成做業的總數爲: {}'.format(len(homework_list)))

因此其實這樣寫,和上面的實例2是同樣的效果喲,而且省略了釋放鎖的步驟,由於with默認自動釋放鎖,這樣就不怕忘記釋放鎖而致使代碼報錯了;

5.線程池

接下來學習一下線程池,也就是ThreadPoolExecutor,線程池在構造實例的時候,會傳入max_workers參數來設置線程池中最多能同時運行的線程數目;

線程池實例對象有兩個很是使用的方法,submit方法和map方法:

  • submit(self, fn, *args, **kwargs):提交線程須要執行的任務(函數名和參數)到線程池中,並返回該任務的句柄,用於提交單個任務;

  • map(self, fn, *iterables, timeout=None, chunksize=1):相似高階函數map,能夠提交任務,且傳遞一個可迭代對象,返回任務處理迭代對象的結果;

from concurrent.futures import ThreadPoolExecutor
import requests

def fetch_url(url):
    result = requests.get(url=url, )
    return result.text

# 建立10個線程隊列的線程池
pool = ThreadPoolExecutor(10)

# 獲取任務返回對象
a = pool.submit(fetch_url, 'http://www.baidu.com')
# 取出返回的結果

x = a.result()
print(x)  # 得到百度的源碼

6.全局解釋器鎖

  • 儘管Python徹底支持多線程編程, 可是解釋器的C語言實現部分在徹底並行執行時並非線程安全的,實際上,解釋器被一個全局解釋器鎖保護着 ,它確保任什麼時候候都只有一個Python線程執行;

  • GIL最大的問題就是Python的多線程程序並不能利用多核CPU的優點, 就是由於GIL的存在,使得一個進程的多個線程在執行任務的時候,一個CPU時 間片內,只有一個線程可以調度到CPU上運行;

  • 所以CPU密集型的程序通常不會使用Python實現,能夠選擇Java,GO等語言;

  • 可是對於非CPU密集型程序,例如IO密集型程序,多數時間都是對網絡IO的等待,所以Python的多線程徹底能夠勝任;

對於全局解釋器鎖的解決方案:

  • 使用multiprocessing建立進程池對象,實現多進程併發,這樣就可以使用多CPU計算資源;

  • 使用C語言擴展,將計算密集型任務轉移給C語言實現去處理,在C代碼實現部分能夠釋放GIL;

多線程和多進程解決方案:

  • 若是想要同時使用多線程和多進程,最好在程序啓動時,建立任何線程以前,先建立一個單例的進程池, 而後線程使用一樣的進程池來進行它們的計 算密集型工做,這樣相似於線程調用了進程,完成了CPU密集型任務,進程也利用了多CPU的優點;

參考:https://www.9xkd.com/user/plan-view.html?id=4240003103

相關文章
相關標籤/搜索