@@ -40,11 +40,20 @@ class Runner(_RunnerBase):
4040 Standard simulation runner class. (Uncoupled simulations.)
4141 """
4242
43- from multiprocessing import Manager
43+ _manager = None
4444
45- _manager = Manager ()
46- _lock = _manager .Lock ()
47- _queue = _manager .Queue ()
45+ @classmethod
46+ def _init_manager (cls ):
47+ """
48+ Initialise the shared-memory Manager the first time a Runner is
49+ constructed in the parent process. Deferred from class definition time
50+ so that importing this module does not fork a manager process before
51+ OpenMM threads have been started.
52+ """
53+ if cls ._manager is None :
54+ from multiprocessing import Manager
55+
56+ cls ._manager = Manager ()
4857
4958 def __init__ (self , system , config ):
5059 """
@@ -73,6 +82,16 @@ def __init__(self, system, config):
7382 # Call the base class constructor.
7483 super ().__init__ (system , config )
7584
85+ # Initialise the shared-memory manager lazily so that importing this
86+ # module does not fork a manager process before OpenMM threads exist.
87+ Runner ._init_manager ()
88+
89+ # Create Lock and Queue as instance attributes so that they are
90+ # pickled as manager proxies and shared correctly across all spawned
91+ # worker processes, preventing race conditions on the GPU pool.
92+ self ._lock = Runner ._manager .Lock ()
93+ self ._queue = Runner ._manager .Queue ()
94+
7695 # Store the array of lambda values for energy sampling.
7796 if self ._config .lambda_energy is not None :
7897 self ._lambda_energy = self ._config .lambda_energy .copy ()
0 commit comments