forked from tylov-fork/PractRand
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathRNG_multithreading.txt
114 lines (93 loc) · 5.17 KB
/
RNG_multithreading.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
========================================================================
Use of PractRand Random Number Generators in multithreaded programs:
========================================================================
The RNGs in PractRand are thread-safe so long as the user does not try
to share RNG instances between threads. While a lock could be added to
an RNG to allow it to be shared, that is highly undesirable from a
performance perspective. The recommended solution is to have a seperate
RNG object for each thread. The easiest way to do that is to tell the
compiler that you want the RNG instance located in thread-local-storage.
On MSVC you do that by prefixing the type name with "__declspec(thread)".
On GCC you do that by prefixing the type name with "__thread". Most
other major C++ compiler use one of those two.
Also, when picking an RNG for a multithreaded application, it may be
wise to go slightly higher on statistical quality and statespace size
than you would for an equivalent single-threaded application. See
RNG_engines.txt for lists of recommended RNGs and their quality
ratings. See RNG_parallel.txt for more on when high quality RNGs are
needed to avoid inter-RNG correlation issues.
The next question is, how do you seed each threads RNG? The simple
answer is, the auto-seeding mechanism is thread-safe, so you can just
initialize each RNG with the parameter PractRand::SEED_AUTO.
However, in order for PractRand auto-seeding to be fully thread safe,
some guidelines must be followed:
1. call PractRand::initialize_PractRand() at the start of main, and
2. do NOT permit multithreading to occur prior to the start of main().
It's fine to use RNGs prior to main(), even auto-seeded RNGs... so long
as only one thread exists at the time. Once initialize_PractRand() has
run everything else should be fully thread-safe.
The solution recommended for programs that may need more control over
the seeding process is:
1. As before, pick an RNG algorithm and declare a global instance in
thread local storage. However, initialize it with SEED_NONE instead
of SEED_AUTO.
2. Also declare a polymorphic entropy pool and a mutex of some kind
(on Win32 I use a CRITICAL_SECTION). These should NOT be in thread
local storage, just ordinary global variables.
3. Before the RNG is used, and before any other threads are launched,
initialize the entropy pool with one or more calls to the add_entropy
methods. The standard method is to call add_entropy_automatically(),
though that currently is a very poor implementation on unrecognized
platforms (it's fine on windows and *nix). Then seed the RNG in the
main thread from the entropy pool.
4. Each time another thread is created, lock the mutex, use the
entropy pool to seed the RNG instance local to the new thread, then
unlock the mutex.
So, all together, the simple version looks like this on MSVC:
//in the .h file:
extern __declspec(thread) PractRand::RNGs::Polymorphic::hc256 rng;
//in the .cpp file:
__declspec(thread) PractRand::RNGs::Polymorphic::hc256 rng(PractRand::SEED_AUTO);
Or like this on GCC:
//in the .h file:
extern __thread PractRand::RNGs::Polymorphic::hc256 rng;
//in the .cpp file:
__thread PractRand::RNGs::Polymorphic::hc256 rng(PractRand::SEED_AUTO);
The complex version looks like this on MSVC:
//in the .h file:
extern __declspec(thread) PractRand::RNGs::Polymorphic::hc256 rng;
extern CRITICAL_SECTION thread_startup_lock;
extern PractRand::RNGs::Polymorphic::sha2_based_pool entropy_pool;
//in the .cpp file:
__declspec(thread) PractRand::RNGs::Polymorphic::hc256 rng(PractRand::SEED_AUTO);
CRITICAL_SECTION thread_startup_lock;
PractRand::RNGs::Polymorphic::sha2_based_pool entropy_pool;
//inside main(): (or elsewhere, as long as it runs prior to multithreading or RNG use)
InitializeCriticalSection(&thread_startup_lock);
entropy_pool.add_entropy_automatically();
//additional entropy_pool.add_entropy* calls go here if needed
entropy_pool.flush_buffers();
rng.seed(entropy_pool);
//inside per-thread initialization function:
EnterCriticalSection(&thread_startup_lock);
rng.seed(entropy_pool);
LeaveCriticalSection(&thread_startup_lock);
On linux / gcc the complex version looks more like:
//in the .h file:
extern __thread PractRand::RNGs::Polymorphic::hc256 rng;
extern pthread_mutex_t thread_startup_lock;
extern PractRand::RNGs::Polymorphic::sha2_based_pool entropy_pool;
//in the .cpp file:
__thread PractRand::RNGs::Polymorphic::hc256 rng(PractRand::SEED_AUTO);
pthread_mutex_t thread_startup_lock;
PractRand::RNGs::Polymorphic::sha2_based_pool entropy_pool;
//inside main(): (or elsewhere, as long as it runs prior to multithreading or RNG use)
pthread_mutex_init( &thread_startup_lock, NULL);
entropy_pool.add_entropy_automatically();
//additional entropy_pool.add_entropy* calls go here if needed
entropy_pool.flush_buffers();
rng.seed(entropy_pool);
//inside per-thread initialization function:
pthread_mutex_lock( &thread_startup_lock);
rng.seed(entropy_pool);
pthread_mutex_unlock( &thread_startup_lock);