Anyone have experience making NCurses thread-safe, say with pthreads?
I could wrap the entire library in one giant mutex, but I'd like to go with finer granularity, if that's plausible. However, I am not sure how fine I can carve up the mutexes.
I ran across these on a google search, but have you tried out use_window() and use_screen() to see if it delivers as advertised? I guess if I use recursive mutex wrappers around the scope listed, I should be able to cut down on the granularity.
It turns out that most of my calls to NCurses do not take very long - just wrapping them in mutexes seems to do the job. If I find that things are slowing down, I will introduce more mutexes and slice it down to a finer granularity.
This seems like a very clean design that avoids lock contentions, and I can avoid the possibility of deadlocks by not trying to reduce granularity by introducing more mutexes.
Please correct me if I'm wrong - the main ideas are:
1. Implement the queue using a mutex + condition-variable: it is essentially a pile of callbacks
2. Use one GUI thread that talks to NCurses
3. Any other thread that wants to draw to the GUI sends, instead, a message to the queue and broadcasts the condition variable
4. When the GUI thread runs out of things to do in the queue, it waits on the condition-variable
To implement the callback mechanism to be put on the queue stack may require some creating of classes for each possible type of GUI operation. Am I right in guessing that most of the coding work will be here, in codifying the objects/states as functors so I can callback later?
Yep -- you've got the idea. The infrastructure takes a little more work, but the result is that the application is a lot easier to maintain and extend.
When I tried my old implementation of a giant mutex around NCurses with multiple threads calling NCurses, Valgrind's drd complained about race conditions. I wonder what does it consider as a race condition? I mean, if both thread1 and thread2 are writing to windowX that's wrapped by a mutex, I guess drd must call that a race condition because it can't tell if the threads are writing to overlapping spaces on the screen, can it?
I will start implementing this one GUI thread with an action queue later today. If thread1 puts an action for windowX in the queue and then thread2 puts an action for windowX in the queue, I wonder how does drd determine if this is a race condition or not? Thread1 depends on network conditions and thread2 depends on the user, so I wonder how drd can know...
Thinking through the single-threaded idea, I first got stuck with the constructor and destructor calls to WINDOW* in pthreads [newwin() and delwin()] because it's rather odd to turn these types of calls into functors (how do I return a WINDOW* later through a callback? deleting is less an issue).
Then I realized, I want to create all the windows before I start my threads and I want to destroy all the windows after I join(), so actually, there is no need to encapsulate them into functors. It turns out, I may only have two methods so far that need to be functorized - both are calls to print to a window, one plain, and one scrolling.
So the basic idea behind a single NCurses GUI thead is, there is only one consumer thread, but there may be many producer threads. Central to the design is a shared, thread-safe Action queue. Any thread may produce an Action and post it to this queue for later execution (like a functor or a callback). However, only the GUI thread may read from this queue and act on it. All my actions so far involve writing to an NCurses WINDOW* encapsulated inside my own NWindow class.
Testing over the last few days using Valgrind/drd, I have found many parts of my code that cause problems. For example, when calling some boost date time conversion functions, I hit std::locale(), which is not threadsafe.
The biggest problem I am dealing with at this point involves the data that I put in the Action queue, in particular, an NWindow instance that will receive the action. The Action queue is thread-safe, but I must be extremely careful not to modify any NWindow instance in my producer threads, lest Valgrind/drd flag a race condition between the consumer thread and one of the producer threads.
Right down, I am trying to cut down my NWindow class to be as state-less and as const as possible in order to reduce problems.
Thought I could just read a C book on pthreads and be able to use it in C++ - how foolish was I.
Apparently this kind of code is no good:
1 2 3 4 5 6 7 8 9 10 11
#include <string>
usingnamespace std;
class Foo
{
void Bar()
{
string foo( "this is no good inside a thread!" );
}
};
Why? AFAIK, std::string always leads to heap allocation ( foo is not on the stack ), does reference counting, and does other multithreading-hostile activities which causes Valgrind/drd to flag all sorts of problems.
Of course, without Valgrind/drd, the app chugs along happily... ...so you would think.
Wish I could find a good book on pthreads and C++, but it looks like I will have to learn by trying (and doing a lot of testing along the way)...
Hmm... ...this is kind of a funky case where you are defining a temporary string that looks like it's going on the stack, but instead, is reference-counted on the heap. I think I was getting race-conditions when checking with Valgrind/drd - it could be that Valgrind/drd is not smart enough to figure this out.
Heck, I'm not smart enough to figure this one out (and don't have enough time), so I've just coded around these issues with automatic char arrays, which Valgrind/drd is fine with.
Anyhow, thank you for the sanity-check, PanGalactic; it's much appreciated when trying out new packages/paradigms.
BTW, I am looking for a replacement for tzset() that won't make Valgrind/drd complain... ...I've found localtime_r(), ctime_r(), etc... which work well as replacements for localtime() and ctime(), but no luck with tzset() yet.
I am using it in conjunction with putenv() to figure out Wallclock time for various time-zones.