日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
說說Python里關(guān)于線程安全的那些事兒

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

成都創(chuàng)新互聯(lián)公司是一家專注于成都網(wǎng)站建設(shè)、成都網(wǎng)站制作與策劃設(shè)計,清原網(wǎng)站建設(shè)哪家好?成都創(chuàng)新互聯(lián)公司做網(wǎng)站,專注于網(wǎng)站建設(shè)十余年,網(wǎng)設(shè)計領(lǐng)域的專業(yè)建站公司;建站業(yè)務(wù)涵蓋:清原等地區(qū)。清原做網(wǎng)站價格咨詢:028-86922220

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

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

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

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

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

 
 
 
 
  1. from threading import Thread, Lock
  2. number = 0
  3. def target():
  4.     global number
  5.     for _ in range(1000000):
  6.         number += 1
  7. thread_01 = Thread(targettarget=target)
  8. thread_02 = Thread(targettarget=target)
  9. thread_01.start()
  10. thread_02.start()
  11. thread_01.join()
  12. thread_02.join()
  13. print(number)

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

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

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

 
 
 
 
  1. 1459782
  2. 1379891
  3. 1432921

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

2. 什么是原子操作?

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

它有點(diǎn)類似數(shù)據(jù)庫中的 事務(wù)。

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

 
 
 
 
  1. L.append(x)
  2. L1.extend(L2)
  3. x = L[i]
  4. x = L.pop()
  5. L1[i:j] = L2
  6. L.sort()
  7. x = y
  8. x.field = y
  9. D[x] = y
  10. D1.update(D2)
  11. D.keys()

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

 
 
 
 
  1. ii = i+1
  2. L.append(L[-1])
  3. L[i] = L[j]
  4. D[x] = D[x] + 1

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

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

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

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

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

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

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

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

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

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

  • LOAD_GLOBAL:加載全局變量
  • LOAD_ATTR:加載屬性,獲取 update 方法
  • LOAD_FAST:加載 new 變量
  • CALL_FUNCTION:調(diào)用函數(shù)
  • POP_TOP:執(zhí)行更新操作

但我們要知道真正會引導(dǎo)數(shù)據(jù)沖突的,其實不是讀操作,而是寫操作。

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

3. 實現(xiàn)人工原子操作

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

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

因此,我們使用加鎖的方法,對例子一進(jìn)行一些修改,使其具備“原子性”。

 
 
 
 
  1. from threading import Thread, Lock
  2. number = 0
  3. lock = Lock()
  4. def target():
  5.     global number
  6.     for _ in range(1000000):
  7.         with lock:
  8.             number += 1
  9. thread_01 = Thread(targettarget=target)
  10. thread_02 = Thread(targettarget=target)
  11. thread_01.start()
  12. thread_02.start()
  13. thread_01.join()
  14. thread_02.join()
  15. print(number)

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

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

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

  • Event
  • Condition
  • Queue

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

他是如何做到的呢?

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

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

分享題目:說說Python里關(guān)于線程安全的那些事兒
網(wǎng)頁地址:http://m.5511xx.com/article/djoosjg.html