-
Notifications
You must be signed in to change notification settings - Fork 85
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Worker Thread Discussion #31
Comments
The pain point with libstdc++ / libc++ is as follows: if we ever want to ship pre-built binaries (for example to let users do libstdc++ is forward compatible using versioned symbols: we can build against older versions and still safely run against a newer version. This also means we have to find a libstdc++ version which already is C++11 feature complete (for threads) and at the same time old enough for us to support newer versions. This is for example what the Python community is doing for building native wheels: they spin up Docker container with an old CentOS and compile+link their native code their. If we decide this is worth the hassle and makes our life easier then I'm all up for changing the implementation to be in C++ linking in libstdc++ / libc++ and keeping a C interface to the outside. But we should be aware of its downsides. For reference: this is what I'm doing in https://github.com/daniel-j-h/libosrmc. |
If we want to go the abstraction route: the C interfaces resemble the C++ RAII pattern (constructor / destructor pair) in that you always have
and This is equivalent to class thing {
thing();
~thing();
void push(int);
int pop();
}; works beautifully with FFI bindings, hides implementation details behind a For a thread abstraction you would have an interface maybe something like typedef struct sweep_thread* sweep_thread_s; // opaque pointer
sweep_thread_s sweep_thread_construct(FnPtr, Args); // constructor
void sweep_thread_destruct(sweep_thread_s); // destructor
void sweep_thread_join(sweep_thread_s); // arbitrary functionality and then in the POSIX implementation file you fill in the blanks with the pthread API, e.g. storing the The interface (the We're already using this technique throughout the libsweep library. Your question about the queue and where to hold onto it: the queue's lifetime depends on the device, so I would store a queue object in the |
@daniel-j-h thanks for the detailed response. That was extremely helpful. I took another stab at it today, which resulted in some success. |
Saw the pull request for the C++11 implementation. Are you just experimenting or advocating that we move forward with the C++ route? |
Experimenting, but I also thought about your plans to integrate more high level algorithms with the sdk and how to do this, since we probably need C++ there anyway. Hm..
FYI I'm out till Tuesday.
…On January 27, 2017 8:03:07 PM GMT+01:00, dcyoung ***@***.***> wrote:
Saw the pull request for the C++11 implementation. Are you just
experimenting or advocating that we move forward with the C++ route?
|
While providing an internal io thread is probably a good default, I think there should be enough flexibility in the design of the library such that a user can provide its own thread or work completely single threaded. |
Hm I understand how dependency injection could help there - not sure how to do this in an elegant way though. We would have to provide ways in the C API to set threading functionality for the C++ implementation to use. Which means we have to provide a thread abstraction. The reason we want to switch the implementation to C++ is exactly because we don't want to provide this abstraction ourselves. @MikeGitb do you have ideas here? |
Sorry, I expressed myself poorly. All I meant was that there should be some function that can be called by the user and e.g. returns after one scan (e.g. just like
This is really just a sketch, but I would imagine, that people on less powerful embedded systems would appreciate it, if you give them full control over what threads run in their application. Wether |
Update: we switched the implementation to C++11. This should unblock further progress here. I'm still thinking about the semantics here: the worker thread should probably put scans into a bounded (how large?) first-in last-out queue. This would give users a chance to poll slowly for scans without the issue of getting out of sync. But it would also mean the users seeing a drop in scans in case the user is not able to poll for scans fast enough. Ideas on how to design this wrt. simplicity, usability and making it easy for users to understand the semantics? |
As I said, I think there should remain a low level interface that pretty much works like the current and doesn't spin up a thread or anything like that. The way I see it, the serial interface on most operating systems will already provide some degree of buffering, so that might actually be enough in some cases and if not the user can use whatever threading strategy is native to his application. However, I realize now that this isn't that important as this is an open source-library anyway and if a user really needs full control he can just hack the lib. That being said, I think your suggested design sounds pretty reasonable for the default interface (although I beleive you mean first-in-first-out) e.g.:
|
Ah, yes @MikeGitb good catch - you are right. I agree with you on all points. The problem I see here is with slow user polling. But dropping scans seems to be a trade-off we have to make here. Re. the bounded queue's size: with the device's max. motor speed of 10 Hz I would say the queue should be of length somewhere between 10--20 max. to buffer 1--2s worth of scans. Users should be able to keep up with that or we start dropping scans. |
Well, if the user doesn't keep up you are going to loos scans one way or another I don't think there is a way around it. I would however make the buffer size configurable by the user. |
In case you are looking for inspiration: Here is an untested version of libsweep that uses a thread internally but doesn't change the interface: https://github.com/MikeGitb/sweep-sdk/tree/worker_thread/libsweep. |
@MikeGitb Thanks for the inspiration! The process of creating and then joining a thread wasn't quite as simple without the other changes you made. Instead I opted to implement a detached background thread that is created when scanning starts and cleans itself up when scanning ends. With a thread safe queue, we shouldn't have to deal with waiting or thread management ourselves. The process works like this:
High level (simple) use case is then unchanged: sweep_device_start_scanning(sweep, &error);
...
for (int32_t num_scans = 0; num_scans < 10; ++num_scans) {
sweep_scan_s scan = sweep_device_get_scan(sweep, &error);
...
sweep_device_stop_scanning(sweep, &error);
... Low level use case is then: sweep_device_attempt_start_scanning(sweep, &error);
...
for (int32_t num_scans = 0; num_scans < 10; ++num_scans) {
sweep_scan_s scan = sweep_device_get_scan_direct(sweep, &error);
...
sweep_device_stop_scanning(sweep, &error);
... I've implemented all of this in the scan-worker branch |
Looking forward to it. Don't forget to make stop_thread an atomic variable. |
Updated. Good catch. Edit: looks like the travis build failed after changing the bool to an atomic variable. I'll have to investigate this. |
Updated. Seems to work on Travis builds now. PR is up #70 |
Resolved in b867c37 |
We've discussed the need to introduce a worker thread for IO. However, the implementation must consider cross-platform compatibility. The debate is whether to...
Shift the
libsweep
library's implementation to C++, while still maintaining the C interface. The C++ implementation would allow for all threading to handled by C++11 standard thread library, and avoid the need for thread abstraction. This would both consolidate the code. @daniel-j-h You mentioned that this requires us to link in libstdc++/libc++ which is a pain for abi stability and shipping portable binaries. Could you elaborate on those two points a bit? Isn't shipping platform specific binaries pretty standard?Abstract the threading into a shared interface that is platform agnostic, and then provide platform specific implementations.
For option 2 (abstraction), we could do this in a similar fashion to
serial.h
+ (serial_unix.c
orserial_win.c
). In this case, we'd have something likesweep_threading.h
+ (sweep_threading_unix.c
orsweep_threading_win.c
). This interface would define an abstracted thread, mutex, conditional var, and single producer single consumer queue. Then the methods from theserial
andsweep
files would make use of the threads and queues via this abstracted interface. The unix implementation could use pthreads. I'm still open to suggestions for the win implementation but it could use WINAPI. Using WINAPI would require the inclusion of <windows.h>. Any complications with the build process or bindings there? @daniel-j-hWhat do we think about the pros/cons of both?
Given that the current style of sweepSDK is to avoid global state...
how should we store and reference the working queue? I spent a while today trying to implement a queue and a worker thread that conformed to the limitations of no global state and the Pimpl idiom, but I didn't get very far.... I'm still getting the hang of the Pimpl idom :/ @daniel-j-h if you've got a plan for how to accomplish this I'm happy to collaborate however I can.
The text was updated successfully, but these errors were encountered: