Now let's look at some frequent problems that come up
when writing multi-threaded applications.
First, make sure to keep track of the mutex and
condition variables that are specifically used with a given shared resource.
What that means, for instance,
is that when defining these variables make sure to write immediately a comment,
which shared resource, which operation, which other piece of shared state,
do you want this synchronization variable to be used with.
For instance, you're creating a mutex m1 and
you want to use it to protect the state of a file, file 1.
Next, make sure that if a variable or
a piece of code is protected with a mutex in one portion of your code,
that you're always consistently protecting that same variable, or
that same type of operation with the same mutex everywhere else in your count.
Basically, a common mistake is that sometimes we
simply forget to use the lock/unlock construct.
And therefore, sometimes access the variable in a safe way.
And if we don't use the lock and
unlock, then it won't be accessed in a safe way period.
Some compilers will sometimes generate warnings or, or
even errors, to tell us that there is a potentially dangerous situation, where
shared variable is and isn't used with a mutex in different places in the code.
Or, maybe they will generate a warning to tell us that there is
a lock construct that's not followed by the appropriate unlock construct.
So certainly you can rely on compilers and tools to help avoid mistakes but
it's just easier not to make them in the first place.
Another common mistakes that's just as bad as not locking a resource,
is to use different mutexes for a single resource.
So, some threads read the same file by locking mutex m1,
and other threads write to the same file by locking mutex m2.
At the same time, different threads can hold different mutexes and
they can perform concurrently operations on this file,
which is not what we want to be happening.
So this scenario can lead to these undesirable situation, actually
dangerous situations where different types of accesses happen concurrently.
Also it's important to make sure that when you're using a signal or
a broadcast you're actually signaling the correct condition.
That's the only way that you can make sure that the correct set of
threads are potentially going to be notified.
Again, using comments when you are declaring these conditions can be helpful.
Also make sure that you're not using signal when you
actually need to use broadcast.
Note that the opposite is actually safe.
If you need to use signal but use broadcast, that's fine.
You will still end up waking up one thread or more.
And you will not affect the correctness of the program.
You may just end up affecting its performance.
But that's not as dangerous.
Remember that with a signal only one thread will be woken up to proceed.
And if, when the condition occurred we had
more than one thread waiting on the condition.
The remaining threads will continue to wait.
And in fact they may continue to wait possibly indefinitely.
Using a signal instead of a broadcast can also possibly cause deadlocks.
And we'll talk about that shortly.
You also have to remember that the use of signal or broadcast or
rather, the order of signal or broadcast.
Doesn't do anything about making any kind of
priority guarantees as far as which one of the threads will execute next.
As we explained in the previous example,
the execution of the threads is not directly controlled by
the order in which we issue signals to a condition variables.
Two other common pitfalls spurious wake ups and dead locks,
deserve special attention and we will discuss these two in more detail next.