问题
I was given a simple python program to analyze. It works fine, and outputs 13, 14, and 15 randomly (of course). I can see why 13 and 14 where printed, but I don't understand where 15 came from.
Please explain.
from threading import Thread
import random
import time
import sys
def rwait():
amt = random.uniform(0.01,0.1)
time.sleep(amt)
x = 0
key = True
def lockx():
global key
while not key:
time.sleep(0)
rwait()
key = False
def unlockx():
global key
key = True
def A():
global x
rwait()
lockx()
reg = x
reg = reg+1
rwait()
x = reg
unlockx()
def B():
global x
rwait()
lockx()
reg = x
reg = reg+2
rwait()
x = reg
unlockx()
def main():
global x
x = 12
p1 = Thread(target=B)
p1.start()
A()
p1.join()
print("x=",x)
for k in range(20):
main()
回答1:
Three different things can happen:
thread A and B read
x
before it is changed, thenthread A writes its result (13), and
thread B writes its result (14),
and the second thread to write wins.
thread A or B reads
x
first, and writes before the other thread reads. Result: 15, as either A reads 12, adds one and writes 13, then B reads 13 and writes 15, or vice-versa.
回答2:
Your function names seem to imply they're performing locking, which they are not. This is for two reasons:
- Accesses to
key
are not guaranteed atomicity. - Even if they were, there is a race between the time
key
is read and its value isTrue
, and the time it is used and set toFalse
.
As a result, your two threads end up modifying shared (global, in this case) state in an unsynchronised fashion. Therefore, any of three scenarios are possible:
x
is only incremented by 1 -B
has executed wholly afterx
was read byA
but before the incremented value was stored back.x
is only incremented by 2 - same scenario as above withA
andB
reversed.x
is incremented by 3 -A
orB
executes wholly beforeB
orA
, respectively.
To correctly synchronise two threads, you have to use locking. Here's an adaptation of your code, using the facilities provided by Threading:
from threading import Thread, Lock
x = 0
lock = Lock()
def lockx():
global lock
lock.acquire()
def unlockx():
global lock
lock.release()
def A():
global x
lockx()
reg = x
reg = reg+1
x = reg
unlockx()
def B():
global x
lockx()
reg = x
reg = reg+2
x = reg
unlockx()
def main():
global x
x = 12
p1 = Thread(target=B)
p1.start()
A()
p1.join()
print("x=",x)
for k in range(20):
main()
回答3:
You have demonstrated a classical concurrency problem here. Two writers act at the same time, thus potentially overwrite the data written by the other.
If you receive 13, then the thread A
reads before thread B
has written its result and A
writes after B
has written its result.
If you receive 14, then the thread B
reads before thread A
has written its result and B
writes after A
has written its result.
If you receive 15, then one thread reads (and computes and writes) after the other thread has written its result. The order of both threads cannot be determined then.
The more intriguing question is, however, why the locking mechanism (lockx
/unlockx
) obviously does not work. Would it work, you'd always get 15 as a result.
来源:https://stackoverflow.com/questions/19726106/strange-python-threading-output