nub*_*ela 43 python increment thread-safety
我想为实验创建一个非线程安全的代码块,这些是2个线程要调用的函数.
c = 0
def increment():
c += 1
def decrement():
c -= 1
Run Code Online (Sandbox Code Playgroud)
这段代码线程安全吗?
如果没有,我可以理解为什么它不是线程安全的,以及什么样的语句通常会导致非线程安全的操作.
如果它是线程安全的,我怎样才能使它明确地是非线程安全的?
Gle*_*ard 88
不,这段代码是绝对的,显然不是线程安全的.
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
Run Code Online (Sandbox Code Playgroud)
一贯失败.
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
Run Code Online (Sandbox Code Playgroud)
bob*_*nce 28
(注意:global c在每个函数中都需要使代码工作.)
这段代码线程安全吗?
在CPython中,只有一个字节码指令是'原子'的,并且a +=可能不会产生单个操作码,即使所涉及的值是简单的整数:
>>> 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
Run Code Online (Sandbox Code Playgroud)
因此,一个线程可以使用c和1加载到索引6,放弃GIL并让另一个线程执行inc并休眠,将GIL返回到第一个线程,该线程现在具有错误的值.
在任何情况下,什么是原子是一个你不应该依赖的实现细节.字节码可能会在CPython的未来版本中发生变化,并且在不依赖于GIL的Python的其他实现中,结果将完全不同.如果您需要线程安全,则需要一个锁定机制.
小智 15
确保我建议使用锁:
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
Run Code Online (Sandbox Code Playgroud)
synchronized装饰器还可以帮助保持代码易于阅读.
Joh*_*ooy 10
很容易证明您的代码不是线程安全的.你可以通过在关键部分使用睡眠来增加看到竞争状态的可能性(这只是模拟一个缓慢的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
Run Code Online (Sandbox Code Playgroud)
由于 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
Run Code Online (Sandbox Code Playgroud)
多个线程共享的每个资源都必须有锁。
| 归档时间: |
|
| 查看次数: |
16401 次 |
| 最近记录: |