Python:无锁机制下多个线程并发访问函数
当多个线程同时访问同一个函数时,我们是否需要明确地实现锁机制呢?
我有一个使用线程的程序。这个程序里有两个线程,t1
和 t2
。t1
用于执行 add1()
,而 t2
用于执行 subtract1()
。这两个线程同时访问同一个函数 myfunction(caller,num)
。
1. 我在这个程序中定义了一个简单的锁机制,使用了一个变量 functionLock
。这个做法可靠吗?还是说我们需要修改它?
import time, threading
functionLock = '' # blank means lock is open
def myfunction(caller,num):
global functionLock
while functionLock!='': # check and wait until the lock is open
print "locked by "+ str(functionLock)
time.sleep(1)
functionLock = caller # apply lock
total=0
if caller=='add1':
total+=num
print"1. addition finish with Total:"+str(total)
time.sleep(2)
total+=num
print"2. addition finish with Total:"+str(total)
time.sleep(2)
total+=num
print"3. addition finish with Total:"+str(total)
else:
time.sleep(1)
total-=num
print"\nSubtraction finish with Total:"+str(total)
print '\n For '+caller+'() Total: '+str(total)
functionLock='' # release the lock
def add1(arg1, arg2):
print '\n START add'
myfunction('add1',10)
print '\n END add'
def subtract1():
print '\n START Sub'
myfunction('sub1',100)
print '\n END Sub'
def main():
t1 = threading.Thread(target=add1, args=('arg1','arg2'))
t2 = threading.Thread(target=subtract1)
t1.start()
t2.start()
if __name__ == "__main__":
main()
程序的输出结果如下:
START add
START Sub
1. addition finish with Total:10
locked by add1
locked by add1
2. addition finish with Total:20
locked by add1
locked by add1
3. addition finish with Total:30
locked by add1
For add1() Total: 30
END add
Subtraction finish with Total:-100
For sub1() Total: -100
END Sub
2. 如果我们不使用锁,是否可以?
即使我不使用上面程序中定义的锁机制,两个线程 t1
和 t2
的结果也是一样的。这是否意味着 Python 在多个线程访问同一个函数时会自动实现锁?
不使用锁 functionLock
的程序输出结果:
START add
START Sub
1. addition finish with Total:10
Subtraction finish with Total:-100
For sub1() Total: -100
END Sub
2. addition finish with Total:20
3. addition finish with Total:30
For add1() Total: 30
END add
谢谢!
4 个回答
虽然我对Python了解不多,但我觉得这跟其他语言差不多:
只要函数里面没有用到在外面声明的变量(这些变量可以被多个线程共享),那么就不需要使用锁。看起来你的函数是符合这个条件的。
不过,输出到控制台的内容可能会出现混乱。
在你的代码里,你自己实现了一个叫做 自旋锁 的东西。虽然这样做是可以的,但我觉得在Python中不太推荐,因为可能会导致性能问题。
我用一个大家都知道的搜索引擎(以
G
开头)搜索了“python lock”。其中一个前几条结果就是这个:Python中的线程同步机制。这看起来是个不错的入门文章。关于代码本身:当你对一个共享资源进行的操作不是原子操作时,就应该加锁。目前看起来你的代码中没有这样的资源。
除了这个讨论串中关于忙等待变量的其他评论外,我想指出的是,你没有使用任何原子交换操作,这可能会导致并发错误。即使你的测试执行没有出现这些问题,但如果在不同的时间重复执行多次,可能会出现以下情况:
线程 #1 执行 while functionLock!=''
,结果得到 False
。然后,线程 #1 被中断(被抢占去执行其他任务),接着线程 #2 执行同样的代码 while functionLock!=''
,也得到 False
。在这个例子中,两个线程都进入了关键区域,这显然不是你想要的。特别是在任何修改 total
的代码行中,结果可能不是你预期的,因为两个线程可以同时在这个区域。看看下面的例子:
total
的初始值是 10
。为了简单起见,假设 num
始终是 1。线程 #1 执行 total+=num
,这个操作实际上分为三个步骤:(i) 读取 total
的值,(ii) 将它与 num
相加,(iii) 将结果存回 total
。如果在步骤 (i) 后,线程 #1 被抢占,线程 #2 执行 total-=num
,那么 total
就变成了 9
。然后,线程 #1 恢复执行,但它已经读取了 total = 10
,所以它加上 1,结果将 11
存入 total
变量。这实际上让线程 #2 的减法操作变成了无效操作。
注意,在 @ron-klein 提到的维基百科文章中,代码使用了 xchg
操作,这个操作可以原子性地交换一个寄存器和一个变量。这对于锁的正确性是至关重要的。总之,如果你想避免难以调试的并发错误,绝不要自己实现锁来替代原子操作。
[编辑] 我刚注意到实际上 total
是你代码中的一个局部变量,所以这种情况不可能发生。然而,我相信你并不知道这是你代码正常工作的原因,因为你说“这是否意味着当多个线程访问同一个函数时,Python 会自动实现锁”,这并不正确。请尝试在 myfunction
的开头添加 global total
,然后多次执行线程,你应该会在输出中看到错误。[/编辑]