<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Python 一定要知道的坑—線程安全

          共 3498字,需瀏覽 7分鐘

           ·

          2020-12-20 08:45


          在并發(fā)編程時,如果多個線程訪問同一資源,我們需要保證訪問的時候不會產生沖突,數據修改不會發(fā)生錯誤,這就是我們常說的 線程安全

          那什么情況下,訪問數據時是安全的?什么情況下,訪問數據是不安全的?如何知道你的代碼是否線程安全?要如何訪問數據才能保證數據的安全?

          本篇文章會一一回答你的問題。

          1. 線程不安全是怎樣的?

          要搞清楚什么是線程安全,就要先了解線程不安全是什么樣的。

          比如下面這段代碼,開啟兩個線程,對全局變量 number 各自增 10萬次,每次增量 1。

          from threading import Thread, Lock

          number = 0

          def target():
              global number
              for _ in range(1000000):
                  number += 1

          thread_01 = Thread(target=target)
          thread_02 = Thread(target=target)
          thread_01.start()
          thread_02.start()

          thread_01.join()
          thread_02.join()

          print(number)

          正常我們的預期輸出結果,一個線程自增100萬,兩個線程就自增 200 萬嘛,輸出肯定為 2000000 。

          可事實卻并不是你想的那樣,不管你運行多少次,每次輸出的結果都會不一樣,而這些輸出結果都有一個特點是,都小于 200 萬。

          以下是執(zhí)行三次的結果

          1459782
          1379891
          1432921

          這種現象就是線程不安全,究其根因,其實是我們的操作 number += 1 ,不是原子操作,才會導致的線程不安全。

          2. 什么是原子操作?

          原子操作(atomic operation),指不會被線程調度機制打斷的操作,這種操作一旦開始,就一直運行到結束,中間不會切換到其他線程。

          它有點類似數據庫中的 事務。

          在 Python 的官方文檔上,列出了一些常見原子操作

          L.append(x)
          L1.extend(L2)
          x = L[i]
          x = L.pop()
          L1[i:j] = L2
          L.sort()
          x = y
          x.field = y
          D[x] = y
          D1.update(D2)
          D.keys()

          而下面這些就不是原子操作

          i = i+1
          L.append(L[-1])
          L[i] = L[j]
          D[x] = D[x] + 1

          像上面的我使用自增操作 number += 1,其實等價于 number = number + 1,可以看到這種可以拆分成多個步驟(先讀取相加再賦值),并不屬于原子操作。

          這樣就導致多個線程同時讀取時,有可能讀取到同一個 number 值,讀取兩次,卻只加了一次,最終導致自增的次數小于預期。

          當我們還是無法確定我們的代碼是否具有原子性的時候,可以嘗試通過 dis 模塊里的 dis 函數來查看

          當我們執(zhí)行這段代碼時,可以看到 number += 1 這一行代碼,由兩條字節(jié)碼實現。

          • BINARY_ADD :將兩個值相加

          • STORE_GLOBAL:將相加后的值重新賦值

          每一條字節(jié)碼指令都是一個整體,無法分割,他實現的效果也就是我們所說的原子操作。

          當一行代碼被分成多條字節(jié)碼指令的時候,就代表在線程線程切換時,有可能只執(zhí)行了一條字節(jié)碼指令,此時若這行代碼里有被多個線程共享的變量或資源時,并且拆分的多條指令里有對于這個共享變量的寫操作,就會發(fā)生數據的沖突,導致數據的不準確。

          為了對比,我們從上面列表的原子操作拿一個出來也來試試,是不是真如官網所說的原子操作。

          這里我拿字典的 update 操作舉例,代碼和執(zhí)行過程如下圖

          從截圖里可以看到,info.update(new) 雖然也分為好幾個操作

          • LOAD_GLOBAL:加載全局變量

          • LOAD_ATTR:加載屬性,獲取 update 方法

          • LOAD_FAST:加載 new 變量

          • CALL_FUNCTION:調用函數

          • POP_TOP:執(zhí)行更新操作

          但我們要知道真正會引導數據沖突的,其實不是讀操作,而是寫操作。

          上面這么多字節(jié)碼指令,寫操作都只有一個(POP_TOP),因此字典的 update 方法是原子操作。

          3. 實現人工原子操作

          在多線程下,我們并不能保證我們的代碼都具有原子性,因此如何讓我們的代碼變得具有 “原子性” ,就是一件很重要的事。

          方法也很簡單,就是當你在訪問一個多線程間共享的資源時,加鎖可以實現類似原子操作的效果,一個代碼要嘛不執(zhí)行,執(zhí)行了的話就要執(zhí)行完畢,才能接受線程的調度。

          因此,我們使用加鎖的方法,對例子一進行一些修改,使其具備原子性。

          from threading import Thread, Lock


          number = 0
          lock = Lock()


          def target():
              global number
              for _ in range(1000000):
                  with lock:
                      number += 1

          thread_01 = Thread(target=target)
          thread_02 = Thread(target=target)
          thread_01.start()
          thread_02.start()

          thread_01.join()
          thread_02.join()

          print(number)

          此時,不管你執(zhí)行多少遍,輸出都是 2000000.

          4. 為什么 Queue 是線程安全的?

          Python 的 threading 模塊里的消息通信機制主要有如下三種:

          1. Event

          2. Condition

          3. Queue

          使用最多的是 Queue,而我們都知道它是線程安全的。當我們對它進行寫入和提取的操作不會被中斷而導致錯誤,這也是我們在使用隊列時,不需要額外加鎖的原因。

          他是如何做到的呢?

          其根本原因就是 Queue 實現了鎖原語,因此他能像第三節(jié)那樣實現人工原子操作。

          原語指由若干個機器指令構成的完成某種特定功能的一段程序,具有不可分割性;即原語的執(zhí)行必須是連續(xù)的,在執(zhí)行過程中不允許被中斷。

          我們的文章到此就結束啦,如果你喜歡今天的Python 實戰(zhàn)教程,請持續(xù)關注Python實用寶典。

          有任何問題,可以在公眾號后臺回復:加群,回答相應紅字驗證信息,進入互助群詢問。

          原創(chuàng)不易,希望你能在下面點個贊和在看支持我繼續(xù)創(chuàng)作,謝謝!

          點擊下方閱讀原文可獲得更好的閱讀體驗

          Python實用寶典 (pythondict.com)
          不只是一個寶典
          歡迎關注公眾號:Python實用寶典

          瀏覽 46
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲性爱网址 | 孕妇操逼视频 | 亚洲一区免费 | 欧美爱爱视频 | 亚洲高清无码在线视频 |