12.1、多線程:互斥鎖、遞歸鎖、信號量、事件

来源:https://www.cnblogs.com/progor/archive/2018/02/09/8433975.html
-Advertisement-
Play Games

互斥鎖: 為什麼要有互斥鎖:由於多線程是並行的,如果某一線程取出了某一個數據將要進行操作,但它還沒有那麼快執行完操作,這時候如果另外一個線程也要操作這個數據,那麼這個數據可能會因為兩次操作而發生錯誤 import time,threading x=6 def run1(): print("run1我... ...



互斥鎖:

  • 為什麼要有互斥鎖:由於多線程是並行的,如果某一線程取出了某一個數據將要進行操作,但它還沒有那麼快執行完操作,這時候如果另外一個線程也要操作這個數據,那麼這個數據可能會因為兩次操作而發生錯誤
import time,threading

x=6
def run1():
    print("run1我拿到了數據:",x)
    print("我現在還不想操作,先睡一下")
    time.sleep(3)
    print("再看一下數據,穩一穩",x)

def run2():
    global x
    print("run2我拿到了數據:", x)

    x=5
    print(x)

t1=threading.Thread(target=run1)
t2=threading.Thread(target=run2)

t1.start()
t2.start()
t1.join()
t2.join()

image

 

  • 而多線程的互斥鎖機制本質上是:申請一個鎖,A線程拿了鑰匙【acquire】之後,如果B也想拿到鑰匙是不行的,只有等A把鑰匙還回來【release】才行
  • 如何使用互斥鎖:
    1. 定義一個鎖對象:鎖對象=threading.Lock()
    2. 請求鎖:鎖對象.acquire()
    3. 釋放鎖:鎖對象.release()

使用互斥鎖來更改上段代碼

import time,threading

x=6
def run1():
    lock.acquire()
    global x
    print("run1我拿到了數據,x=",x)
    print("我現在還不想操作,先睡一下")
    time.sleep(3)
    print("再看一下數據,穩一穩,x=",x)
    x+=1
    print("run1操作完畢:x=",x)
    lock.release()
def run2():
    lock.acquire()
    global x
    print("run2我拿到了數據:", x)
    x+=1
    print("run2操作完畢:x=",x)
    lock.release()

lock=threading.Lock()#生成一個鎖對象
t1=threading.Thread(target=run1)
t2=threading.Thread(target=run2)

t1.start()
t2.start()
start_time=time.time()
t1.join()
t2.join()
print("最終的x=",x)
print(time.time()-start_time)#3.0多說明,由於受到鎖的影響,run2要等待run1釋放lock,所以變成了串列

 

這種互斥鎖在操作系統中可以稱作“臨界區”,如果想瞭解更多:

https://baike.baidu.com/item/%E4%B8%B4%E7%95%8C%E5%8C%BA/8942134?fr=aladdin

image


遞歸鎖:

  • 為什麼要有遞歸鎖:互斥鎖本質上是阻止其他線程進入,如果有兩個需要阻止其他線程進入的操作【像兩個人過獨木橋】,那麼需要兩個鎖,而想要鎖上第二個如果直接用第一個鎖的acquire會失敗,因為第一個鎖還沒release,我們可以選擇再定義一個互斥鎖對象來acquire,但這僅僅是兩層的情況下,如果多層的吧,那麼就需要定義好幾個互斥鎖對象了【而且由於對象變多,有時候會因為互相調用鎖而發生死鎖】。遞歸鎖就是為了處理這種情況,遞歸鎖對象允許多次acquire和多次release
    • 發生死鎖的情況[A拿到A鎖,想要拿B鎖,B拿著B鎖,想要A鎖]

【以過獨木橋為例】:橋只能容一個人通過,A只能看得到北邊橋上有沒有人,看不到南邊橋有沒有人,當他看到北邊橋沒人就會過橋,等到他到橋中間才能看到南邊橋有沒有人,B情況相反:【於是當兩個人一起過橋的時候就會發生死鎖】

import threading,time




"""
A只能看得到北邊橋上有沒有人,看不到南邊橋有沒有人,
當他看到北邊橋沒人就會過橋,等到他到橋中間才能看到南邊橋有沒有人
"""
def A():
    lockNorth.acquire()#拿到北邊橋的鎖
    print("A過橋北")
    time.sleep(3)#過橋中
    lockSorth.acquire()#企圖過到南邊橋,
    print("A過橋南")
    time.sleep(3)  # 過橋中
    lockSorth.release()
    lockNorth.release()
    print("A過橋成功")

"""
B只能看得到南邊橋上有沒有人,看不到北邊橋有沒有人,
當他看到南邊橋沒人就會過橋,等到他到橋中間才能看到北邊橋有沒有人
"""
def B():
    lockSorth.acquire()  # 企圖過到南邊橋,
    print("B過橋南")
    time.sleep(3)  # 過橋中
    lockNorth.acquire()  # 拿到北邊橋的鎖
    print("B過橋北")
    time.sleep(3)  # 過橋中
    lockNorth.release()
    lockSorth.release()
    print("B過橋成功")


lockNorth=threading.Lock()
lockSorth=threading.Lock()

tA=threading.Thread(target=A)
tB=threading.Thread(target=B)
tA.start()
tB.start()

tA.join()
tB.join()

image

 

  • 遞歸鎖的本質是:本質上還是一個鎖,但如果在一個線程裡面可以多次acquire。【因為只有一個鎖,所以不會發生互相調用的死鎖,而因為可以多次調用,所以可以鎖多次】
  • 如何使用遞歸鎖:
    1. 定義一個鎖對象:遞歸鎖對象=threading.RLock()
    2. 請求鎖:鎖對象.acquire()
    3. 釋放鎖:鎖對象.release()

使用遞歸鎖來解決上面的死鎖問題:

import threading,time

"""
A只能看得到北邊橋上有沒有人,看不到南邊橋有沒有人,
當他看到北邊橋沒人就會過橋,等到他到橋中間才能看到南邊橋有沒有人
"""
def A():
    lock.acquire()#拿到北邊橋的鎖
    print("A過橋北")
    time.sleep(3)#過橋中
    lock.acquire()#企圖過到南邊橋,
    print("A過橋南")
    time.sleep(3)  # 過橋中
    lock.release()
    lock.release()
    print("A過橋成功")

"""
B只能看得到南邊橋上有沒有人,看不到北邊橋有沒有人,
當他看到南邊橋沒人就會過橋,等到他到橋中間才能看到北邊橋有沒有人
"""
def B():
    lock.acquire()  # 拿南橋鎖,
    print("B過橋南")
    time.sleep(3)  # 過橋中
    lock.acquire()  # 企圖拿北橋的鎖
    print("B過橋北")
    time.sleep(3)  # 過橋中
    lock.release()
    lock.release()
    print("B過橋成功")


lock=threading.RLock()

tA=threading.Thread(target=A)
tB=threading.Thread(target=B)
tA.start()
tB.start()

tA.join()
tB.join()

image

【由於本質是一把鎖,A拿到鎖後,B要等待】

 


信號量:

  • 什麼是信號量:

image

信號量可以限制進入的線程的數量。

  • 如何使用信號量:
    1. 創建信號量對象:信號量對象=threading.BoundedSemaphore(x),x是限制進程的數量
    2. 當有進程需要進入的時候,調用acquire()來減少信號量:信號量對象.acquire()
    3. 當有進程離開的時候,調用release()來增加信號量:信號量對象.release()
import threading,time


def run():
    s.acquire()
    print("hello")
    time.sleep(1.5)
    s.release()

s=threading.BoundedSemaphore(3)#限制3個

threading_list=[]
for i in range(12):#創建12個線程
    obj=threading.Thread(target=run)
    obj.setDaemon(True)  # 設置守護線程,避免干擾主線程運行,並行等待
    obj.start()

for i in range(4):
    print("")#為了把結果分割,可以清楚看出分為了三組
    time.sleep(1.5)
#結果分為三組是因為運行的太快了,三個線程裝入的時間差太小

image


事件:

  • 什麼是事件:當發生線程發生一件事的時候如果要提醒另外一個線程,使用事件。雙方共用該事件對象【等待的一方會阻塞而進行等待】,當一方更改事件對象的時候,另外一方也能知道【以讀者-寫者為例:讀者要等寫者告訴他去讀才會去讀,寫者寫完後要設置一個事件,當該事件設置時,讀者就會來讀】
  • 如何使用事件:
    1. 創建事件對象:事件對象=threading.Event()
    2. 設置事件:事件對象.set()    判斷事件是否set:事件對象.is_set(),等待事件set:事件對象.wait()
    3. 清除事件:事件對象.clear() 
import threading,time


def read():
    while True:
        if event.is_set():
            print("事件已設置,我要讀了!!!!")
            time.sleep(1)
        else:#事件未設置
            print("還沒寫好,我要等咯")
            event.wait()#那麼就等著咯
            #如果等到了
            print("終於等到了!那麼我又可以讀了")
            time.sleep(1)

def write():
    event.clear()#初始設空
    while True:
        time.sleep(3)#
        event.set()#設置事件,一旦set,那麼讀者wait就有返回了,讀者可以繼續運行了
        print("write:寫好了")
        time.sleep(2)#等人讀
        event.clear()#清除事件


event=threading.Event() #創建事件對象

t1=threading.Thread(target=write)
t2=threading.Thread(target=read)

t1.start()
t2.start()
t1.join()
t2.join()

"""結果顯示:讀者確實一直在等待寫者寫好"""

image



您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Okhttp的使用沒有httpClient廣泛,網上關於Okhttp設置代理的方法很少,這篇文章完整介紹了需要註意的方方面面。 上一篇博客中介紹了socks代理的入口是創建 時傳入一個 對象。 OkHttp client通過 創建,可以通過定製 和`java.net.SocketFactory`來實 ...
  • 本文描述http client使用socks代理過程中需要註意的幾個方面:1,socks5支持用戶密碼授權;2,支持https;3,支持讓代理伺服器解析DNS; 使用代理創建Socket 從原理上來看,不管用什麼http客戶端(httpclient,okhttp),最終都要轉換到 的創建上去,看到代 ...
  • 統介紹: 1.系統採用主流的 SSM 框架 jsp JSTL bootstrap html5 (PC瀏覽器使用) 2.springmvc +spring4.3.7+ mybaits3.3 SSM 普通java web(非maven, 附贈pom.xml文件) 資料庫:mysql 3.開發工具:mye ...
  • 外圈的層次可以依賴內層,反之不可以;內圈核心的實體代表業務,不可以依賴其所處的技術環境。 這是著名軟體大師Bob大叔提出的一種架構,也是當前各種語言開發架構。乾凈架構提出了一種單向依賴關係,從而在邏輯上形成一種向上的抽象系統。 這種乾凈的架構圖如下: 依賴規則Dependency Rule 上圖中同 ...
  • 本文要點預覽:因為軟體系統的分散式特點以及開發團隊的分佈性,瞭解軟體架構的基礎變得越來越重要。而在過度設計和毫無設計之間,我們應該把註意力放在對軟體系統有重大影響的決策和權衡上。好的架構師應該是團隊的活躍分子,不僅能夠進行代碼協作,還能為團隊提供技術指導。軟體架構中的溝通環節極具挑戰性。C4 模型對 ...
  • We are in the changing world , exspecially in interenet,more and more developing structure appeared in latest years from MVC to SOA ,from SOA to Micro ...
  • ORM的特點之一是:把表映射成類,把行作為實例,把欄位作為屬性。 ...
  • #環境配置基於windows操作系統 (一) 環境配置 (1)執行下麵的命令(前提:已經安裝python環境,可以參考之前發的python筆記(一)) pip install -U selenium (2)http://docs.seleniumhq.org/download/ (網站打不開的話就翻 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...