you share the same list on all threads and its mutated. its hard to debug because when you add a print it will behave differently. but this [97, 32, 17, 15, 57, 97, 63, 72, 60, 8]
must be a state inside shuffle
. shuffle holds the list (the same list that exists in all threads) and changes it more than once. at the time the threads are called the state is [97, 32, 17, 15, 57, 97, 63, 72, 60, 8]
. The values don't get copied immanently, they are copied in another thread so you can't guarantee when they will be copied.
An example of what shuffle produces before the shuffle is done:
[31, 64, 88, 7, 68, 85, 69, 3, 15, 47] # initial value (rands)
# ex.submit() is called here
# shuffle() is called here
# shuffle starts changing rand to:
[31, 64, 88, 47, 68, 85, 69, 3, 15, 7]
[31, 64, 15, 47, 68, 85, 69, 3, 88, 7]
[31, 64, 15, 47, 68, 85, 69, 3, 88, 7]
[31, 64, 69, 47, 68, 85, 15, 3, 88, 7]
[31, 64, 85, 47, 68, 69, 15, 3, 88, 7] # threads may be called here
[31, 64, 85, 47, 68, 69, 15, 3, 88, 7] # or here
[31, 64, 85, 47, 68, 69, 15, 3, 88, 7] # or here
[31, 85, 64, 47, 68, 69, 15, 3, 88, 7]
[85, 31, 64, 47, 68, 69, 15, 3, 88, 7] # value when the shuffle has finished
shuffle source code:
def shuffle(self, x, random=None):
if random is None:
randbelow = self._randbelow
for i in reversed(range(1, len(x))):
# pick an element in x[:i+1] with which to exchange x[i]
j = randbelow(i+1)
x[i], x[j] = x[j], x[i]
# added this print here. that's what prints the output above
# your threads are probably being called when this is still pending
print(x)
... other staff here
so if your input is [17, 72, 97, 8, 32, 15, 63, 97, 57, 60]
and your output is [97, 15, 97, 32, 60, 17, 57, 72, 8, 63]
the shuffle has "steps in the middle between that". your threads get called in the "steps in the middle"
An example without mutation, in general try to avoid sharing data between threads because its really hard to get it right:
def work_with_rands(i, rands):
print('in function', i, rands)
def foo(a):
random.seed(random.randrange(999912)/9)
x = [None]*len(a)
for i in a:
_rand = random.randrange(len(a))
while x[_rand] is not None:
_rand = random.randrange(len(a))
x[_rand] = i
return x
def main():
rands = [random.randrange(100) for _ in range(10)]
with futures.ProcessPoolExecutor() as ex:
for i in range(4):
new_rands = foo(rands)
print("<{}> rands before submission: {}".format(i, new_rands ))
ex.submit(work_with_rands, i, new_rands )
<0> rands before submission: [84, 12, 93, 47, 40, 53, 74, 38, 52, 62]
<1> rands before submission: [74, 53, 93, 12, 38, 47, 52, 40, 84, 62]
<2> rands before submission: [84, 12, 93, 38, 62, 52, 53, 74, 47, 40]
<3> rands before submission: [53, 62, 52, 12, 84, 47, 93, 40, 74, 38]
in function 0 [84, 12, 93, 47, 40, 53, 74, 38, 52, 62]
in function 1 [74, 53, 93, 12, 38, 47, 52, 40, 84, 62]
in function 2 [84, 12, 93, 38, 62, 52, 53, 74, 47, 40]
in function 3 [53, 62, 52, 12, 84, 47, 93, 40, 74, 38]