-
Notifications
You must be signed in to change notification settings - Fork 30
/
Copy path00_player_separate_process.py
150 lines (125 loc) · 4.94 KB
/
00_player_separate_process.py
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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
"""
Replaying a file in a separate process
======================================
With a :class:`~mne_lsl.player.PlayerLSL` object, you can replay an MNE-Python readable
file as a valid LSL stream. This is very useful for testing or debugging purposes. The
created LSL stream is managed in the main process, as a separate thread. Thus, if the
code running the main process is hogging all of the CPU resources, then the
:class:`~mne_lsl.player.PlayerLSL` object may not be able to keep up with the LSL stream
and to push (publish) new data when it should. This is why it is often useful to run the
:class:`~mne_lsl.player.PlayerLSL` object in a separate process.
Single process execution
------------------------
Let's start by demonstrating the issue with a single process where the main thread is
hogging all of the CPU resources, leaving almost nothing for the
:class:`~mne_lsl.player.PlayerLSL` thread to run and mock the LSL stream.
"""
# sphinx_gallery_thumbnail_path = '_static/tutorials/multiprocessing.png'
import multiprocessing as mp
import time
from mne.io import read_raw_fif
from mne_lsl.datasets import sample
from mne_lsl.player import PlayerLSL as Player
raw = read_raw_fif(sample.data_path() / "sample-ecg-raw.fif", preload=False)
raw.crop(0, 3).load_data()
player = Player(raw, chunk_size=200, n_repeat=1, name="example-process")
player
# %%
# .. note::
#
# Note that we use a ``chunk_size`` of 200 samples which does replicate a real-time
# scenario where the LSL stream is usually using finer granularity (specific to the
# device and LSL stream application). However, this large chunk size provides
# stability to the documentation build on our CIs.
#
# The smaller the chunk size, the more resources are needed by the
# :class:`~mne_lsl.player.PlayerLSL` object to keep up with the LSL stream. This is why
# with a chunk size of 200 samples, we don't observe a difference between a main thread
# giving time to the player to run (scenario 1, while loop with :func:`time.sleep` calls
# to create idle time) and a main thread hogging all of the CPU resources (scenario 2,
# while loop without any idle time).
# scenario 1: give time to the player to run
start = time.time()
player.start()
while player.running:
time.sleep(0.5)
stop = time.time()
print(f"Elapsed time (free time): {stop - start:.2f} s")
# scenario 2: hog all of the CPU resources
start = time.time()
player.start()
while player.running:
pass
stop = time.time()
print(f"Elapsed time (hogging): {stop - start:.2f} s")
# clean-up resources
del player
# %%
# If you run the code above with a ``chunk_size`` of 10 samples, or of 1 sample, you
# will observe the following difference in elapsed time:
#
# - chunk size of 10 samples:
#
# - 3.5 seconds for scenario 1 (due to one extra sleep call)
# - 7.4 seconds for scenario 2
#
# - chunk size of 1 sample:
#
# - 3.5 seconds for scenario 1 (due to one extra sleep call)
# - 68.8 seconds for scenario 2
#
# Multi-process execution
# -----------------------
#
# To avoid this problem, we can run the :class:`~mne_lsl.player.PlayerLSL` object in a
# separate process. This way, the main process can hog all of the CPU resources without
# affecting the player process.
#
# This can be achieved in the following ways:
#
# - use the command-line entry point to run the player in a separate terminal
#
# .. code-block:: bash
#
# $ mne_lsl_player sample-ecg-raw.fif --chunk-size 1 --n-repeat 1
#
# - use the python API to run the player in a separate python interpreter
# - use the python API and the ``multiprocessing`` module to run the player in a
# separate process
#
# Here, we demonstrate the last option.
def player_process(raw):
"""Process which runs the player."""
from mne_lsl.player import PlayerLSL
player = PlayerLSL(raw, chunk_size=200, n_repeat=1, name="example-process-2")
player.start()
while player.running:
pass
process = mp.Process(target=player_process, args=(raw,))
process.start()
print("Player started in a separate process!")
process.join()
# %%
# This is still a bit too simplistic. The process takes time to actually start, thus
# when the print is emitted, the player has not yet started. To fix this, we can use a
# shared variable to signal when the player has started. This shared variable can also
# be used to interrupt the player process from the main process.
def player_process(raw, status):
"""Process which runs the process."""
from mne_lsl.player import PlayerLSL
player = PlayerLSL(raw, chunk_size=200, name="example-process-3")
player.start()
status.value = 1
while status.value:
pass
player.stop()
manager = mp.Manager()
# integer value, used to store 0 or 1 to turn off the player
status = manager.Value("i", 0)
process = mp.Process(target=player_process, args=(raw, status))
process.start()
while status.value != 1:
pass # wait for the player to actually start
print("Player started in a separate process!")
status.value = 0 # stops the player
process.join()