Make your flows as simple as possible.
Try not to use global variables. Global constants (actual constants that never change) are fine. When you need to use global or general variables, you need to protect them with some mutex / lock (semaphore, monitor, ...).
Make sure you really understand how your mutex works. There are several different implementations that can work in different ways.
Try organizing your code so that critical sections (the places where you hold the lock (blocks) of a certain type) as quickly as possible. Keep in mind that some functions may block (sleep or wait for something and not allow the OS to allow this thread to continue for some time). Do not use them while holding any locks (if it is absolutely necessary or during debugging, as sometimes they may show other errors).
Try to understand what actually more threads actually do for you. Blindly throwing more problems into a problem very often gets worse. Different threads compete for processor and locks.
Avoiding a dead end requires planning. Try not to get more than one lock at a time. If this is unavoidable, select the order that you will use to acquire and release locks for all flows. Make sure you know what a dead end really means.
Debugging multithreaded or distributed applications is difficult. If you can do most of the debugging in one streaming environment (perhaps even just by making other threads sleep), you can try to clear up some obscure central errors before switching to multi-threaded debugging.
Always think about what other threads can do. Comment this in your code. If you are doing something in a certain way because you know that at that time no other thread should access a specific resource, write a big comment saying this.
You might want to wrap mutex lock / unlock calls in other functions, such as:
int my_lock_get (lock_type lock, const char * file, unsigned line, const char * msg) {
thread_id_type me = this_thread(); logf("%u\t%s (%u)\t%s:%u\t%s\t%s\n", time_now(), thread_name(me), me, "get", msg); lock_get(lock); logf("%u\t%s (%u)\t%s:%u\t%s\t%s\n", time_now(), thread_name(me), me, "in", msg);
}
And a similar version for unlocking. Please note: all functions and types used in this are compiled and not too much based on any API.
Using something like this, you can return if there is an error and use a perl script or something like this to run queries in your logs to check where everything went wrong (for example, matching locks and unlocks).
Please note that blocking may be required for your print or logging function. Many libraries already have this built in, but not all. These locks should not use the print version of lock_ [get | release] or you will have infinite recursion.