On 11.03.2017 16:03 Daniel Polski wrote:
> Thank you Albrecht and Greg,
You're welcome.
> I currently don't know the exact incoming data rate, but I'd guess it
> *can* be in the range of 100-500 packets/second, but normally much
> lower. The data is pretty simple though (integers/doubles/strings) and
> won't need much processing. It's normally not the same raw data
> container beeing updated fast, but instead many containers beeing
> updated with new numbers.
OK, the reason why I was asking is that the data rate and hence the
display update rate is very important. Human beings can only notice
updates in a range of up to 60 fps or maybe a little more. Everything
above ~20 fps (or maybe 15) looks like smooth motion, hence videos use
25-30 fps. Everything above 30 fps would be wasted CPU cycles.
If your incoming data processing worker threads would (try to) trigger
display updates with 100-500 fps this would (a) be wasted effort and (b)
likely overload the CPU/GPU. This would block the worker threads and the
entire system. Not a good idea, IMHO.
So I'd say as a rule of thumb: if your expected data rate is lower than
50 data points per second (dpps) you may try to trigger a display update
for each new data point. If it is far lower than 30 dpps on average this
has the advantage that every update will be done instantly.
My initial reply would have been better suited to this "slow data
model": for every new data item your worker thread would have called
Fl::awake(func,data) once
and the main thread would have updated the GUI (widgets). I won't
elaborate about Fl::awake() now since this doesn't seem useful for your
case with high data rate.
In your case you will certainly have to limit the display updates to a
sensible frame rate. One way to do this is to let the worker thread(s)
update the data in internal buffers (one for each thread or for each
data source) and run a timer that polls these internal buffers every
50ms (20fps) or so. If new data arrived the main FLTK thread (in a timer
callback) can safely update widgets and call redraw() on all changed
widgets.
Another way would be to let the worker threads keep an internal clock
and gather data until the next display update needs to be done (maybe
every 40 ms = 25 fps) and only then call Fl::awake(func,data).
> I wrapped up an experiment in the style of my initial idea, and would
> like some help in understanding if, and if so why, it's not a suitable
> solution to use with fltk or if it has some other drawbacks I'm not
> aware of. (I didn't implement automatic removal from listeners list and
> such things to make the example shorter and focus on the overall idea).
>
> I think I should describe some of the goals in more detail to make it
> easier to understand what I'm doing:
> ...
> Anyway.. Here's my litte experiment:
(code removed)
OK, I took a look at your code and rewrote it somewhat for testing
purposes. For those following this thread: the code uses C++11 features
and I could compile and run my rewritten version under Linux. Compile
commands are documented in the source file and below.
However I got error messages when trying to compile using MinGW (32-bit
or mingw-w64). I don't know why though:
$ g++ --version
g++.exe (x86_64-win32-seh, Built by MinGW-W64 project) 6.1.0
$ g++ -o rptest1 -std=c++11 -DINTERVAL=25 `fltk-config --cxxflags`
rptest1.cxx `fltk-config --ldflags` && ./rptest1
rptest1.cxx:56:8: error: 'mutex' in namespace 'std' does not name a type
std::mutex value_mtx;
^~~~~
rptest1.cxx: In member function 'void widgetData::value(int)':
rptest1.cxx:61:3: error: 'value_mtx' was not declared in this scope
value_mtx.lock();
^~~~~~~~~
rptest1.cxx: In member function 'int widgetData::value()':
rptest1.cxx:72:3: error: 'value_mtx' was not declared in this scope
value_mtx.lock();
^~~~~~~~~
rptest1.cxx: In function 'int main(int, char**)':
rptest1.cxx:137:3: error: 'thread' is not a member of 'std'
std::thread t(receiveData, tData);
^~~
rptest1.cxx:155:3: error: 't' was not declared in this scope
t.join();
^
If anybody knows how to compile the attached file with MinGW I'd be
interested...
Anyway, I could compile the attached file rptest1.cxx under Linux, so
here are my comments:
I didn't change the way your program works. The bad thing is that your
worker threads access FLTK widgets (they call Fl::check() and redraw()
and maybe more). Although this can work to a certain degree it is not
portable nor reliable and may crash in unexpected ways. In my first
tests it appeared to work, even with my modifications that show the
values as widget labels (I added copy_label() and use a working draw()
method).
Well, seems to work? No, it doesn't. It appeared to work with one update
per second (INTERVAL=1000 ms), but it failed when I shortened the update
interval (INTERVAL < 50 ms == 20 fps). Here's what I got under Linux:
$ ./rptest1
Run test with INTERVAL = 30 ms ...
[xcb] Unknown request in queue while dequeuing
[xcb] Most likely this is a multi-threaded client and XInitThreads has
not been called
[xcb] Aborting, sorry about that.
rptest1: ../../src/xcb_io.c:179: dequeue_pending_request: Assertion
`!xcb_xlib_unknown_req_in_deq' failed.
Aborted (core dumped)
Note that calling XInitThreads as the error message suggests would not
help. FLTK uses a single-threaded approach with the window system (here
X11) and the issue arises from the worker threads calling FLTK methods.
But sometimes it works with similar intervals, and sometimes it appears
to hang and doesn't even output the "Run test with INTERVAL ..." message.
This shows exactly what I expected: unreliable and unpredictable
behavior. Please keep this in mind.
I'm interested in what happens if you run this modified version under
Windows with short intervals or even with 1000 ms. And: how do you
compile it? Do you use MS Visual Studio?
Now, how to fix this? Never call Fl::check(), Fl::wait() or other
methods that can access widgets from the worker thread(s). You defined a
mutex that synchronizes access to the incoming data, so I think this is
all you need, if that's necessary at all.
What I would do is start a timer (Fl::add_timeout()) right before
Fl::run(). In the timer callback I'd check if new data arrived in one of
the thread buffers. If necessary, lock the mutex for this check. Then
process the data and update the widgets. Lock the mutex (again) to mark
the data as read (processed) so the worker thread(s) can remove the data
from the buffer(s). After that I'd restart the timer with
Fl::repeat_timeout() with a reasonable interval (20-50 ms). This way you
can decouple worker threads retrieving data and the FLTK main thread
that reads the data from internal buffers and updates the widgets.
Did this help?
Some more notes: calling Fl::lock() from a worker thread to sync with
the main FLTK thread is generally OK but can block the worker thread for
a significant time while the FLTK main loop is updating the display
(drawing to the display is a relatively slow operation). The timer
mechanism proposed above has the advantage that the FLTK threads and the
worker threads need to synchronize (lock) only while each thread
accesses the data buffer. This can be very short and is similar to what
the docs say about "lockless programming". And more than that: you get a
reliable display update rate independent of incoming data.