使用条件变量的 Pthread 程序死锁
该程序试图完成的任务:
该程序应该同步“访客”和“汽车”的多个线程。游客会随机闲逛一段时间,直到决定乘坐汽车。如果他们排在第一位乘车并且有车可用,他们就会乘车,否则他们必须等到他们排在第一位或有车回来。如果没有游客排队,汽车就会按顺序等待,直到有游客想要乘车。
更多背景信息:
我按照接受的答案中的建议使用条件变量重新制作了线程同步程序此处。我知道我走在正确的轨道上,但我的程序由于某种原因仍然陷入僵局,我一生都无法弄清楚为什么。我不知道你能如何帮助我,除非我给你代码,所以这里是:
问题:
1.) 短暂的死锁
2.) 有时访问者首先排队等待有一辆车,但从来没有上过车。
解决方案:
我的代码中存在太多错误...而且我认为,当我修复一个错误时,我经常(无意中)引入另一个错误。我只是不断删除程序的功能,直到消除了所有错误,然后以一种不会使程序陷入僵局的方式一一重新构建功能。谢谢大家的建议。
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.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
您拥有大量代码,因此不太可能有人为您找到所有错误。然而,有一些评论:
互斥体不是信号量。 main() 中的几个 for 循环正在解锁尚未锁定的互斥锁。这几乎肯定是一个错误。从概念上讲,互斥体可以使用信号量来实现,并且您可以使用互斥体和 condvar 来实现信号量,但是您要解锁未锁定的互斥体,这是不正确的。
每个线程都应该锁定一个互斥锁,做一些工作,然后解锁它。线程不应解锁已被另一个线程锁定的互斥体,或抢先解锁它未锁定的互斥体。如果这完全有效,那么这是您当前使用的实现中的一个怪癖,并且不能移植到其他实现。
main 中的第二个 for 循环连续两次锁定同一个互斥锁。您是否已经超越了代码中的这一点?由于您正在循环,因此您锁定它的次数多于解锁它的次数。如果您的代码停在这里,我不会感到惊讶。 (有时互斥体可以是递归的,但默认情况下 pthread 互斥体不是递归的。)
pthread_cond_signal() 实际上只是对 pthread_cond_broadcast() 的优化。使用广播,直到解决了比赛条件。
在启动线程之前,您应该在 main 顶部初始化所有互斥体和条件变量。您可能在这里没有错误,但这不会造成伤害,而且可能会有所帮助。
如果在短期内将所有内容减少到单个互斥体和单个条件变量,您可能会做得更好。看起来您正在尝试对所有内容进行细粒度锁定,但除非您非常小心锁定的顺序,否则您将遇到竞争条件和死锁。
实际上,只有一种模式/模板应该与互斥体和条件变量一起使用:
如果您有一个互斥体和条件变量,则应该尝试使所有同步块看起来像这样。如果您不需要等待,您可以省略 while(...)/wait 内容;如果没有其他线程关心您所做的更改,但如果您的代码看起来不粗略,您可以省略广播就像每个同步块一样,这可能是一个错误。
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.
首先,xscott 是正确的,您错误地使用了互斥体。即使它看起来有效一段时间也没关系,它仍然是错误的,并且可能只是由于纯粹的偶然而看起来有效。
我认为最好的方法是从第一原则构建设计,而不是逐行检查代码。我将描述我认为您所追求的基本算法:
请注意,我没有明确标记唤醒点 - 只是等待。这是最好的方法 - 弄清楚你需要在哪里等待状态改变,然后你只需要在状态改变时进行唤醒(向条件变量发出信号)。
下一步是确定需要互斥体保护的主要共享数据。我明白了:
所以最细粒度的方法是为访客队列设置一个互斥锁(我们可以使用您的
v_mutex
),为汽车队列设置一个互斥锁(c_mutex
),还有一个用于车辆队列(c_mutex
)每辆车 (sc[CARS]
)。或者,您可以使用c_mutex
来保护汽车队列和每辆车的状态;或者只是使用v_mutex
来保护一切。但为了学习,我们将采用更复杂的方法。下一步是确定条件变量有用的等待点。对于
等到访问者队列的头部
,您的每个访问者条件变量(v_cond
)就可以了;对于等到有车空闲
,最简单的方法是添加另一个条件变量 (v_car_cond
)。对于等待占用
,每辆车的条件变量c_cond
是合适的。对于等到不再在车里
,可以使用v_cond
或c_cond
,因为汽车和访客之间存在一对一的联系那一点。不需要v_cond2
。我们现在可以根据 pthreads 原语重写上面的伪代码。在大多数情况下,这非常接近您已有的 - 所以您绝对走在正确的轨道上。首先是访客:
其次是汽车:
上面的情况可以简化 - 只要有
pthread_mutex_unlock()
紧随其后的是同一个互斥体的pthread_mutex_lock()
,解锁/lock 对可以删除。PS:
您不必担心您的汽车以“错误的顺序”加入汽车队列 - 无论如何,它们在公园里行驶时都会失去秩序。如果您确实关心这一点,请在启动任何汽车线程之前将汽车放入主线程中的队列中,并更改汽车代码,以便它在主循环结束时将自身重新添加到队列中。
使用这种方法的整体代码,省略了全局变量和辅助函数,这些都很好,看起来像这样:
我希望这有帮助......
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...