Make the random module thread-safe in Python

2020-02-06 08:51发布

问题:

I have an application requiring the same results given the same random seed. But I find random.randint not threadsafe. I have tried mutex but this does not work. Here is my experiment code (long but simple):

import threading
import random

def child(n, a):
    g_mutex = threading.Lock()
    g_mutex.acquire()
    random.seed(n)
    for i in xrange(100):
        a.append(random.randint(0, 1000))
    g_mutex.release()

def main():
    a = []
    b = []
    c1 = threading.Thread(target = child, args = (10, a))
    c2 = threading.Thread(target = child, args = (20, b))
    c1.start()
    c2.start()
    c1.join()
    c2.join()

    c = []
    d = []
    c1 = threading.Thread(target = child, args = (10, c))
    c2 = threading.Thread(target = child, args = (20, d))
    c1.start()
    c1.join()
    c2.start()
    c2.join()

    print a == c, b == d

if __name__ == "__main__":
    main()

I want to code to print true, true, but it stands a chance to give false, false. How can I make threadsafe randint?

回答1:

Others have pointed out the proper way to use random in a thread safe way. But I feel it's important to point out that the code you wrote would not be thread-safe for anything.

def child(n, a):
    g_mutex = threading.Lock()
    g_mutex.acquire()
    random.seed(n)
    for i in xrange(100):
        a.append(random.randint(0, 1000))
    g_mutex.release()

Each thread is running this method independently. That means that each thread is making their own lock instance, acquiring it, doing work, and then releasing it. Unless every thread is attempting to acquire the same lock, the there is nothing to ensure non-parallel execution. You need to assign a single value to g_mutex outside of the context of your run method.

Edit:

I just want to add that simply switching to a global lock is not guaranteed to do exactly what you said. The lock will ensure that only one thread is generating numbers at a time, but it does not guarantee which thread will start first.



回答2:

You can create separate instances of random.Random for each thread

>>> import random
>>> local_random = random.Random()
>>> local_random.seed(1234)
>>> local_random.randint(1,1000)
967


回答3:

From the documentation for random:

The functions supplied by this module are actually bound methods of a hidden instance of the random.Random class. You can instantiate your own instances of Random to get generators that don’t share state. This is especially useful for multi-threaded programs, creating a different instance of Random for each thread, and using the jumpahead() method to make it likely that the generated sequences seen by each thread don’t overlap.

The documentation doesn't say exactly what this class is, but it does show class random.SystemRandom([seed]), and random.Random([seed]) seems to be the same.

Example:

local_random = random.Random(n)
for i in xrange(100):
    a.append(local_random.randint(0, 1000))