mirror of
https://github.com/python/cpython.git
synced 2025-10-10 00:43:41 +00:00
Fix bugs introduced by rewrite (in particular, time-based initialization
got broken). Also added new method .jumpahead(N). This finally gives us a semi-decent answer to how Python's RNGs can be used safely and efficiently in multithreaded programs (although it requires the user to use the new machinery!).
This commit is contained in:
parent
d7b5e88e8e
commit
d52269bfd0
3 changed files with 67 additions and 17 deletions
|
@ -33,14 +33,15 @@ Else, because no critical sections are implemented internally, calls
|
||||||
from different threads may see the same return values.
|
from different threads may see the same return values.
|
||||||
|
|
||||||
The functions supplied by this module are actually bound methods of a
|
The functions supplied by this module are actually bound methods of a
|
||||||
hidden instance of the \var{random.Random} class. You can instantiate
|
hidden instance of the \var{random.Random} class. You can instantiate your
|
||||||
your own instances of \var{Random} to get generators that don't share state.
|
own instances of \var{Random} to get generators that don't share state.
|
||||||
This may be especially useful for multi-threaded programs, although there's
|
This is especially useful for multi-threaded programs, creating a different
|
||||||
no simple way to seed the distinct generators to ensure that the generated
|
instance of \var{Random} for each thread, and using the \method{jumpahead()}
|
||||||
sequences won't overlap. Class \var{Random} can also be subclassed if you
|
method to ensure that the generated sequences seen by each thread don't
|
||||||
want to use a different basic generator of your own devising: in that
|
overlap. Class \var{Random} can also be subclassed if you want to use a
|
||||||
case, override the \method{random()}, \method{seed()}, \method{getstate()}
|
different basic generator of your own devising: in that case, override the
|
||||||
and \method{setstate()} methods.
|
\method{random()}, \method{seed()}, \method{getstate()},
|
||||||
|
\method{setstate()} and \method{jumpahead()} methods.
|
||||||
|
|
||||||
|
|
||||||
Bookkeeping functions:
|
Bookkeeping functions:
|
||||||
|
@ -68,6 +69,16 @@ Bookkeeping functions:
|
||||||
of the generate to what it was at the time \code{setstate()} was called.
|
of the generate to what it was at the time \code{setstate()} was called.
|
||||||
\end{funcdesc}
|
\end{funcdesc}
|
||||||
|
|
||||||
|
\begin{funcdesc}{jumpahead}{n}
|
||||||
|
Change the internal state to what it would be if \code{random()} were
|
||||||
|
called n times, but do so quickly. \var{n} is a non-negative integer.
|
||||||
|
This is most useful in multi-threaded programs, in conjuction with
|
||||||
|
multiple instances of the \var{Random} class: \method{setstate()} or
|
||||||
|
\method{seed()} can be used to force all instances into the same
|
||||||
|
internal state, and then \method{jumpahead()} can be used to force the
|
||||||
|
instances' states as far apart as you like (up to the period of the
|
||||||
|
generator).
|
||||||
|
\end{funcdesc}
|
||||||
|
|
||||||
Functions for integers:
|
Functions for integers:
|
||||||
|
|
||||||
|
|
|
@ -72,8 +72,8 @@ class Random:
|
||||||
self.gauss_next = None
|
self.gauss_next = None
|
||||||
|
|
||||||
# Specific to Wichmann-Hill generator. Subclasses wishing to use a
|
# Specific to Wichmann-Hill generator. Subclasses wishing to use a
|
||||||
# different core generator should override seed(), random(), getstate()
|
# different core generator should override the seed(), random(),
|
||||||
# and setstate().
|
# getstate(), setstate(), and jumpahead() methods.
|
||||||
|
|
||||||
def __whseed(self, x=0, y=0, z=0):
|
def __whseed(self, x=0, y=0, z=0):
|
||||||
"""Set the Wichmann-Hill seed from (x, y, z).
|
"""Set the Wichmann-Hill seed from (x, y, z).
|
||||||
|
@ -104,6 +104,7 @@ class Random:
|
||||||
|
|
||||||
if a is None:
|
if a is None:
|
||||||
self.__whseed()
|
self.__whseed()
|
||||||
|
return
|
||||||
a = hash(a)
|
a = hash(a)
|
||||||
a, x = divmod(a, 256)
|
a, x = divmod(a, 256)
|
||||||
a, y = divmod(a, 256)
|
a, y = divmod(a, 256)
|
||||||
|
@ -115,11 +116,10 @@ class Random:
|
||||||
|
|
||||||
def getstate(self):
|
def getstate(self):
|
||||||
"""Return internal state; can be passed to setstate() later."""
|
"""Return internal state; can be passed to setstate() later."""
|
||||||
|
|
||||||
return self.VERSION, self._seed, self.gauss_next
|
return self.VERSION, self._seed, self.gauss_next
|
||||||
|
|
||||||
def __getstate__(self): # for pickle
|
def __getstate__(self): # for pickle
|
||||||
self.getstate()
|
return self.getstate()
|
||||||
|
|
||||||
def setstate(self, state):
|
def setstate(self, state):
|
||||||
"""Restore internal state from object returned by getstate()."""
|
"""Restore internal state from object returned by getstate()."""
|
||||||
|
@ -134,6 +134,28 @@ class Random:
|
||||||
def __setstate__(self, state): # for pickle
|
def __setstate__(self, state): # for pickle
|
||||||
self.setstate(state)
|
self.setstate(state)
|
||||||
|
|
||||||
|
def jumpahead(self, n):
|
||||||
|
"""Act as if n calls to random() were made, but quickly.
|
||||||
|
|
||||||
|
n is an int, greater than or equal to 0.
|
||||||
|
|
||||||
|
Example use: If you have 2 threads and know that each will
|
||||||
|
consume no more than a million random numbers, create two Random
|
||||||
|
objects r1 and r2, then do
|
||||||
|
r2.setstate(r1.getstate())
|
||||||
|
r2.jumpahead(1000000)
|
||||||
|
Then r1 and r2 will use guaranteed-disjoint segments of the full
|
||||||
|
period.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not n >= 0:
|
||||||
|
raise ValueError("n must be >= 0")
|
||||||
|
x, y, z = self._seed
|
||||||
|
x = int(x * pow(171, n, 30269)) % 30269
|
||||||
|
y = int(y * pow(172, n, 30307)) % 30307
|
||||||
|
z = int(z * pow(170, n, 30323)) % 30323
|
||||||
|
self._seed = x, y, z
|
||||||
|
|
||||||
def random(self):
|
def random(self):
|
||||||
"""Get the next random number in the range [0.0, 1.0)."""
|
"""Get the next random number in the range [0.0, 1.0)."""
|
||||||
|
|
||||||
|
@ -471,6 +493,17 @@ def _test_generator(n, funccall):
|
||||||
print 'avg %g, stddev %g, min %g, max %g' % \
|
print 'avg %g, stddev %g, min %g, max %g' % \
|
||||||
(avg, stddev, smallest, largest)
|
(avg, stddev, smallest, largest)
|
||||||
|
|
||||||
|
s = getstate()
|
||||||
|
N = 1019
|
||||||
|
jumpahead(N)
|
||||||
|
r1 = random()
|
||||||
|
setstate(s)
|
||||||
|
for i in range(N): # now do it the slow way
|
||||||
|
random()
|
||||||
|
r2 = random()
|
||||||
|
if r1 != r2:
|
||||||
|
raise ValueError("jumpahead test failed " + `(N, r1, r2)`)
|
||||||
|
|
||||||
def _test(N=200):
|
def _test(N=200):
|
||||||
print 'TWOPI =', TWOPI
|
print 'TWOPI =', TWOPI
|
||||||
print 'LOG4 =', LOG4
|
print 'LOG4 =', LOG4
|
||||||
|
@ -515,6 +548,7 @@ paretovariate = _inst.paretovariate
|
||||||
weibullvariate = _inst.weibullvariate
|
weibullvariate = _inst.weibullvariate
|
||||||
getstate = _inst.getstate
|
getstate = _inst.getstate
|
||||||
setstate = _inst.setstate
|
setstate = _inst.setstate
|
||||||
|
jumpahead = _inst.jumpahead
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
_test()
|
_test()
|
||||||
|
|
15
Misc/NEWS
15
Misc/NEWS
|
@ -8,7 +8,12 @@ Standard library
|
||||||
- random.py is now self-contained, and offers all the functionality of
|
- random.py is now self-contained, and offers all the functionality of
|
||||||
the now-deprecated whrandom.py. See the docs for details. random.py
|
the now-deprecated whrandom.py. See the docs for details. random.py
|
||||||
also supports new functions getstate() and setstate(), for saving
|
also supports new functions getstate() and setstate(), for saving
|
||||||
and restoring the internal state of all the generators.
|
and restoring the internal state of the generator; and jumpahead(n),
|
||||||
|
for quickly forcing the internal state to be the same as if n calls to
|
||||||
|
random() had been made. The latter is particularly useful for multi-
|
||||||
|
threaded programs, creating one instance of the random.Random() class for
|
||||||
|
each thread, then using .jumpahead() to force each instance to use a
|
||||||
|
non-overlapping segment of the full period.
|
||||||
|
|
||||||
|
|
||||||
What's New in Python 2.1 alpha 1?
|
What's New in Python 2.1 alpha 1?
|
||||||
|
@ -107,12 +112,12 @@ Core language, builtins, and interpreter
|
||||||
a complicated (but still thread-safe) method using fgets() is used by
|
a complicated (but still thread-safe) method using fgets() is used by
|
||||||
default.
|
default.
|
||||||
|
|
||||||
You can force use of the fgets() method by #define'ing
|
You can force use of the fgets() method by #define'ing
|
||||||
USE_FGETS_IN_GETLINE at build time (it may be faster than
|
USE_FGETS_IN_GETLINE at build time (it may be faster than
|
||||||
getc_unlocked()).
|
getc_unlocked()).
|
||||||
|
|
||||||
You can force fgets() not to be used by #define'ing
|
You can force fgets() not to be used by #define'ing
|
||||||
DONT_USE_FGETS_IN_GETLINE (this is the first thing to try if std test
|
DONT_USE_FGETS_IN_GETLINE (this is the first thing to try if std test
|
||||||
test_bufio.py fails -- and let us know if it does!).
|
test_bufio.py fails -- and let us know if it does!).
|
||||||
|
|
||||||
- In addition, the fileinput module, while still slower than the other
|
- In addition, the fileinput module, while still slower than the other
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue