What this program is trying to accomplish:
This program is supposed to synchronize several threads of "visitors" and "cars". Visitors wander around for a random amount of time until they decide to go on a car ride. If they are first in line for a car ride and a car is available they take a ride, else they must wait till they are first in line or a car comes back. If there are no visitors in line the cars wait in order until a visitor wants to go on a ride.
More background info:
I remade my thread synchronization program using condition variables as suggested in the accepted answer here. I know I am on the right track but my program still deadlocks for some reason and for the life of me I cannot figure out why. I don't know how you can help me unless I give you the code, so here it is:
Problems:
1.) deadlock after a short bit
2.) sometimes a visitor is first in line for a car, but never gets in a car.
The Solution:
There were just too many bugs in my code... and I think as I would fix one I was often (inadvertently) introducing another one. I just kept removing features of the program until I had eliminated all the bugs, then built features back in one by one in a manner that would not deadlock my program. Thank you all for your suggestions.
You've got a lot of code, so it's unlikely that anyone is going to find all of the bugs for you. However, a few comments:
Mutexes are not semaphores. Several of your for loops in main() are unlocking a mutex that you haven't locked yet. This is almost certainly an error. Conceptually, mutexes could be implemented with semaphores, and you could implement a semaphore with a mutex and condvar, but you're unlocking an unlocked mutex, and this is incorrect.
Each thread should lock a mutex, do some work, and then unlock it. A thread should not unlock a mutex which has been locked by another thread, or preemptively unlock a mutex that it didn't lock. If this works at all, it's a quirk in the implementation you are currently using and not portable to other implementations.
Your second for loop in main locks the same mutex twice in a row. Are you getting past this point in the code? Since you're looping, you're locking it more than you're unlocking it. I wouldn't be surprised if your code stops here. (Sometimes mutexes can be recursive, but pthread mutexes are not by default.)
pthread_cond_signal() is really just an optimization over pthread_cond_broadcast(). Use broadcast until you get your race conditions worked out.
You should initialize all of your mutexes and condvars at the top of main before you start your threads. It's possible that you don't have an error here, but it won't hurt, and it could help.
You might do better if you reduce everything down to a single mutex and a single condvar for the short term. It looks like you're trying to do fine grained locking with everything, but unless you're really careful about the order of your locks, you're going to get race conditions and deadlock.
Really, there is only one pattern/template you should use with mutexes and condvars:
If you have a single mutex and condvar, you should try to make all of synchronization blocks look like that. You can omit the while(...)/wait stuff if you don't need to wait, and you can omit the broadcast if no other threads care about the change you've made, but if your code doesn't roughly look like that for each synchronization block, it's probably a bug.
I think you're more comfortable with semaphores. Here's an implementation of semaphores in terms of mutexes and condvars:
Maybe if you implement your solution in terms of semaphores, you'll get the results you're after.
Firstly, xscott is correct that you're using mutexes incorrectly. It doesn't matter if it appears to work for a short while, it's still wrong, and is probably only appearing to work due to sheer chance.
Rather than go through your code line-by-line, I think the best approach is to build up the design from first principles. I would describe the basic algorithm that I think you're after like this:
Note that I don't explicitly mark the wakeup points - just the waits. This is the best approach - figure out where you need to wait for something to change state, then you just need to put the wakeups (signalling the condition variable) whenever that state changes.
The next step would be to identify the major shared data that needs to be protected by mutexes. I see:
So the most granular approach would be to have one mutex for the visitor queue (we can use your
v_mutex
), one for the car queue (c_mutex
) and one for each car (sc[CARS]
). Alternatively, you could usec_mutex
to protect the car queue and the status of every car; or just usev_mutex
to protect everything. But for the sake of learning, we'll go with the more complicated approach.The next step is to identify the waiting points where condition variables will be useful. For
wait until at head of visitor queue
, your per-visitor condition variables (v_cond
) will be fine; forwait until there is a car free
it will be easiest to add another condition variable (v_car_cond
). Forwait until occupied
the per-car condition variablesc_cond
are appropriate. Forwait until not in car anymore
eitherv_cond
orc_cond
could be used, since there's a 1-to-1 nexus between cars and visitors at that point. There is no need forv_cond2
.We can now re-write the pseudo-code above in terms of pthreads primitives. In most cases this is very close to what you already had - so you were definitely on the right track. First the visitor:
Second, the car:
The above can be simplified - wherever there is a
pthread_mutex_unlock()
followed immediately by apthread_mutex_lock()
of the same mutex, the unlock/lock pair can be removed.PS:
You shouldn't worry about your cars joining the car queue in the "wrong order" - they're going to get out of order as they trundle around the park anyhow. If you really care about this, put the cars into the queue in the main thread before starting any of the car threads, and change the car code so that it re-adds itself to the queue at the end of its main loop.
The overall code with this approach, leaving out your global variables and helper functions which are fine, looks like this:
I hope this is helpful...