0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

如何知道你的代碼是否線程安全

科技綠洲 ? 來(lái)源:Python實(shí)用寶典 ? 作者:Python實(shí)用寶典 ? 2023-11-01 11:42 ? 次閱讀

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

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

本篇文章會(huì)一一回答你的問(wèn)題。

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

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

比如下面這段代碼,開啟兩個(gè)線程,對(duì)全局變量 number 各自增 10萬(wàn)次,每次增量 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)

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

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

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

1459782
1379891
1432921

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

2. 什么是原子操作?

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

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

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,其實(shí)等價(jià)于 number = number + 1,可以看到這種可以拆分成多個(gè)步驟(先讀取相加再賦值),并不屬于原子操作。

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

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

圖片

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

  • BINARY_ADD :將兩個(gè)值相加
  • STORE_GLOBAL:將相加后的值重新賦值

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

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

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

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

圖片

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

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

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

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

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

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

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

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

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)

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

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

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

  1. Event
  2. Condition
  3. Queue

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

他是如何做到的呢?

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

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

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 數(shù)據(jù)
    +關(guān)注

    關(guān)注

    8

    文章

    6713

    瀏覽量

    88301
  • 函數(shù)
    +關(guān)注

    關(guān)注

    3

    文章

    4235

    瀏覽量

    61965
  • 代碼
    +關(guān)注

    關(guān)注

    30

    文章

    4670

    瀏覽量

    67764
  • 線程安全
    +關(guān)注

    關(guān)注

    0

    文章

    13

    瀏覽量

    2451
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    知道最短最高效的代碼是什么?

    知道最短最高效的代碼是什么?我先公布我的答案,是下面這個(gè)代碼片: #include int main() { printf(“Hello world!”) } 就是這個(gè),是它
    發(fā)表于 12-01 17:16

    XC32源碼和字符串線程安全

    我正在嘗試我的項(xiàng)目的FrReTOS遷移,我想了解哪些字符串處理函數(shù)是線程安全的,或者不是線程安全的。特別是,我想看看StrudStruts,Strutk,還有一些其他的源
    發(fā)表于 11-26 16:03

    是否知道量程選擇的原理呢?

    是否知道量程選擇的原理呢?
    發(fā)表于 04-29 06:16

    Linux下的線程安全是什么

    Linux下的線程安全原文結(jié)構(gòu)有點(diǎn)亂線程安全:多個(gè)執(zhí)行流對(duì)臨界資源進(jìn)行爭(zhēng)搶訪問(wèn),而不會(huì)造成數(shù)據(jù)二義性和邏輯混亂,成這段代碼的過(guò)程是
    發(fā)表于 07-01 13:34

    什么是線程安全?如何去實(shí)現(xiàn)線程安全?

    什么是線程安全?如何去實(shí)現(xiàn)線程安全?互斥實(shí)現(xiàn)的技術(shù)是什么?有哪些注意事項(xiàng)?同步實(shí)現(xiàn)的技術(shù)是什么?其操作流程有哪些?
    發(fā)表于 07-23 09:57

    有沒有人知道rt-thread的dfs+uffs是否線程安全

    有沒有人知道,dfs+uffs是否線程安全?1、如A線程寫A文件,同時(shí)B線程讀B文件
    發(fā)表于 04-20 11:37

    A線程如何在線程本身識(shí)別變量是否改變

    阻塞獲取可以解決但是這個(gè)B線程是別人代碼寫的。不好修改不想再增加一個(gè)線程去循環(huán)讀取變量X是否改變,再釋放信號(hào)量需求A線程如何在
    發(fā)表于 11-02 11:02

    Python中最常用的幾種線程會(huì)用嗎

    多進(jìn)程編程中的一個(gè)概念,在擁有共享數(shù)據(jù)的多條線程并行執(zhí)行的程序中,線程安全代碼會(huì)通過(guò)同步機(jī)制保證各個(gè)線程都可以正常且正確的執(zhí)行,不會(huì)出現(xiàn)數(shù)
    的頭像 發(fā)表于 10-11 16:07 ?1049次閱讀

    示波器的這些安全操作知道嗎?

    示波器的這些安全操作知道嗎?示波器維修。很多人都知道示波器是用來(lái)干什么的,也知道示波器都有哪些種類和品牌,當(dāng)然也
    發(fā)表于 11-05 11:19 ?1590次閱讀

    什么是線程安全 如何實(shí)現(xiàn)線程安全代碼

    相信有很多同學(xué)在面對(duì)多線程代碼時(shí)都會(huì)望而生畏,認(rèn)為多線程代碼就像一頭難以馴服的怪獸,制服不了這頭怪獸它就會(huì)反過(guò)來(lái)吞噬
    的頭像 發(fā)表于 05-17 12:45 ?1553次閱讀

    什么是線程線程池中線程實(shí)現(xiàn)復(fù)用的原理

    一般建議自定義線程工廠,構(gòu)建線程的時(shí)候設(shè)置線程的名稱,這樣就在查日志的時(shí)候就方便知道是哪個(gè)線程執(zhí)行的代碼
    發(fā)表于 01-29 13:44 ?1597次閱讀

    如何理解線程安全?

    本次分享線程安全的基礎(chǔ)知識(shí)。
    的頭像 發(fā)表于 05-08 15:03 ?786次閱讀
    如何理解<b class='flag-5'>線程</b><b class='flag-5'>安全</b>?

    什么是線程安全?如何理解線程安全?

    在多線程編程中,線程安全是必須要考慮的因素。
    的頭像 發(fā)表于 05-30 14:33 ?1846次閱讀
    什么是<b class='flag-5'>線程</b><b class='flag-5'>安全</b>?如何理解<b class='flag-5'>線程</b><b class='flag-5'>安全</b>?

    線程安全怎么辦

    線程安全一直是多線程開發(fā)中需要注意的地方,可以說(shuō),并發(fā)安全保證了所有的數(shù)據(jù)都安全。 1 線程
    的頭像 發(fā)表于 10-10 15:00 ?299次閱讀
    <b class='flag-5'>線程</b><b class='flag-5'>安全</b>怎么辦

    redis多線程還能保證線程安全

    Redis是一種使用C語(yǔ)言編寫的高性能鍵值存儲(chǔ)系統(tǒng),它是單線程的,因?yàn)槭褂昧硕嗦窂?fù)用的方式來(lái)處理并發(fā)請(qǐng)求。這樣的實(shí)現(xiàn)方式帶來(lái)了很好的性能,但同時(shí)也引發(fā)了一些線程安全方面的問(wèn)題。 在Redis中,由于
    的頭像 發(fā)表于 12-05 10:28 ?1346次閱讀