日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > python >内容正文

python

python线程安全的计数器_+ =运算符在Python中是线程安全的吗?

發布時間:2025/3/15 python 21 豆豆
生活随笔 收集整理的這篇文章主要介紹了 python线程安全的计数器_+ =运算符在Python中是线程安全的吗? 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

+ =運算符在Python中是線程安全的嗎?

我想為實驗創建一個非線程安全的代碼塊,這些是2個線程將要調用的函數。

c = 0

def increment():

c += 1

def decrement():

c -= 1

此代碼線程安全嗎?

如果不是,我可以理解為什么它不是線程安全的,以及哪種類型的語句通常會導致非線程安全的操作。

如果它是線程安全的,如何使它顯式為非線程安全的?

8個解決方案

88 votes

不,該代碼絕對是絕對線程安全的。

import threading

i = 0

def test():

global i

for x in range(100000):

i += 1

threads = [threading.Thread(target=test) for t in range(10)]

for t in threads:

t.start()

for t in threads:

t.join()

assert i == 1000000, i

持續失敗。

i + = 1解析為四個操作碼:加載i,加載1,將兩者相加,然后將其存儲回i。 Python解釋器每100個操作碼切換一次活動線程(通過從一個線程釋放GIL,以便另一個線程可以擁有它)。 (這兩個都是實現細節。)當在加載和存儲之間發生100操作碼搶占時,就會發生競爭狀態,從而允許另一個線程開始遞增計數器。 當它返回到掛起的線程時,它將繼續使用舊值“ i”,并且同時撤消其他線程運行的增量。

使它成為線程安全的很簡單。 添加鎖:

#!/usr/bin/python

import threading

i = 0

i_lock = threading.Lock()

def test():

global i

i_lock.acquire()

try:

for x in range(100000):

i += 1

finally:

i_lock.release()

threads = [threading.Thread(target=test) for t in range(10)]

for t in threads:

t.start()

for t in threads:

t.join()

assert i == 1000000, i

Glenn Maynard answered 2020-07-27T15:56:46Z

28 votes

(注意:每個函數都需要inc才能使代碼正常工作。)

此代碼線程安全嗎?

否。在CPython中,只有一個字節碼指令是“原子的”,即使所涉及的值是簡單的整數,inc也可能不會產生單個操作碼:

>>> c= 0

>>> def inc():

... global c

... c+= 1

>>> import dis

>>> dis.dis(inc)

3 0 LOAD_GLOBAL 0 (c)

3 LOAD_CONST 1 (1)

6 INPLACE_ADD

7 STORE_GLOBAL 0 (c)

10 LOAD_CONST 0 (None)

13 RETURN_VALUE

因此,一個線程可以在裝入c和1的情況下到達索引6,放棄GIL并讓另一個線程進入,該線程執行inc并進入睡眠狀態,將GIL返回到第一個線程,該線程現在具有錯誤的值。

無論如何,原子性是您不應該依賴的實現細節。 字節碼在將來的CPython版本中可能會更改,并且在不依賴GIL的其他Python實現中,結果將完全不同。 如果需要線程安全,則需要鎖定機制。

bobince answered 2020-07-27T15:57:24Z

15 votes

確保我建議使用鎖:

import threading

class ThreadSafeCounter():

def __init__(self):

self.lock = threading.Lock()

self.counter=0

def increment(self):

with self.lock:

self.counter+=1

def decrement(self):

with self.lock:

self.counter-=1

同步裝飾器還可以幫助使代碼易于閱讀。

gillesv answered 2020-07-27T15:57:48Z

10 votes

很容易證明您的代碼不是線程安全的。 您可以通過在關鍵部分使用睡眠來提高查看比賽狀況的可能性(這只是模擬CPU速度慢)。 但是,如果您將代碼運行足夠長的時間,無論如何最終都應該看到競爭狀態。

from time import sleep

c = 0

def increment():

global c

c_ = c

sleep(0.1)

c = c_ + 1

def decrement():

global c

c_ = c

sleep(0.1)

c = c_ - 1

John La Rooy answered 2020-07-27T15:58:10Z

4 votes

簡短的回答:不。

長答案:一般不會。

盡管CPython的GIL使單個操作碼具有線程安全性,但這不是一般行為。 您可能不會假設,即使簡單的操作(例如加法)也是原子指令。 當另一個線程運行時,添加可能僅完成一半。

而且,一旦函數在多個操作碼中訪問一個變量,線程安全性就會消失。 如果將函數體包裝在鎖中,則可以生成線程安全性。 但是請注意,鎖可能會在計算上耗費大量資源并可能產生死鎖。

ebo answered 2020-07-27T15:58:44Z

3 votes

單一操作碼由于具有GIL而具有線程安全性,但除此之外:

import time

class something(object):

def __init__(self,c):

self.c=c

def inc(self):

new = self.c+1

# if the thread is interrupted by another inc() call its result is wrong

time.sleep(0.001) # sleep makes the os continue another thread

self.c = new

x = something(0)

import threading

for _ in range(10000):

threading.Thread(target=x.inc).start()

print x.c # ~900 here, instead of 10000

多個線程共享的每個資源都必須有一個鎖。

Jochen Ritzel answered 2020-07-27T15:56:12Z

2 votes

如果您實際上想使代碼不是線程安全的,并且很有可能在不嘗試一萬次的情況下(或在您真正不希望發生“壞的”事情發生的情況下)就發生“壞的”事情, 您可以通過顯式睡眠來“抖動”您的代碼:

def íncrement():

global c

x = c

from time import sleep

sleep(0.1)

c = x + 1

Rasmus Kaj answered 2020-07-27T15:59:04Z

0 votes

您確定函數遞增和遞減執行沒有任何錯誤嗎?

我認為它應該引發UnboundLocalError,因為您必須明確地告訴Python您要使用名為'c'的全局變量。

因此,將遞增(也遞減)更改為以下內容:

def increment():

global c

c += 1

我認為您的代碼也是線程不安全的。 有關Python中的線程同步機制的本文可能會有所幫助。

ardsrk answered 2020-07-27T15:59:37Z

總結

以上是生活随笔為你收集整理的python线程安全的计数器_+ =运算符在Python中是线程安全的吗?的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。