C 使用条件变量的Pthread程序死锁
此程序试图实现的目标: 这个程序应该同步“访客”和“汽车”的几个线程。游客们会在一段随机的时间里四处游荡,直到他们决定去坐车。如果他们是第一个排队坐车的人,并且有一辆车可供他们乘坐,否则他们必须等到第一个排队或者有车回来。如果没有游客排队,汽车会按顺序等待,直到有游客想乘车 更多背景信息: 我使用接受的答案中建议的条件变量重新编写了线程同步程序。我知道我在正确的轨道上,但我的程序仍然因为某些原因而死锁,而对于我的一生,我不知道为什么。除非我给你密码,否则我不知道你能帮我什么,所以这里是: 问题: 1.)短位后出现死锁 2.)有时,游客排在排队等候汽车的第一位,但从未上车 解决方案:C 使用条件变量的Pthread程序死锁,c,pthreads,deadlock,C,Pthreads,Deadlock,此程序试图实现的目标: 这个程序应该同步“访客”和“汽车”的几个线程。游客们会在一段随机的时间里四处游荡,直到他们决定去坐车。如果他们是第一个排队坐车的人,并且有一辆车可供他们乘坐,否则他们必须等到第一个排队或者有车回来。如果没有游客排队,汽车会按顺序等待,直到有游客想乘车 更多背景信息: 我使用接受的答案中建议的条件变量重新编写了线程同步程序。我知道我在正确的轨道上,但我的程序仍然因为某些原因而死锁,而对于我的一生,我不知道为什么。除非我给你密码,否则我不知道你能帮我什么,所以这里是: 问题:
我的代码中有太多的bug。。。我想当我修复一个时,我经常(无意中)引入另一个。我只是一直删除程序的功能,直到我消除了所有的bug,然后以一种不会使程序死锁的方式一个接一个地重新构建功能。谢谢大家的建议。您有很多代码,因此不太可能有人会为您找到所有的bug。然而,有几点评论: 互斥体不是信号量。main()中的几个for循环正在解锁尚未锁定的互斥锁。这几乎肯定是一个错误。从概念上讲,互斥体可以用信号量实现,也可以用互斥体和condvar实现信号量,但是你解锁了一个未锁定的互斥体,这是不正确的 每个线程应该锁定一个互斥锁,做一些工作,然后解锁它。线程不应解锁已被另一个线程锁定的互斥锁,也不应抢先解锁未锁定的互斥锁。如果这真的有效,那么这是您当前使用的实现中的一个怪癖,无法移植到其他实现中 主循环中的第二个for循环连续两次锁定同一互斥锁。你是否已经通过了代码中的这一点?既然你在循环,你锁定它的次数比解锁的次数多。如果你的代码停在这里,我不会感到惊讶。(有时互斥锁可以是递归的,但pthread互斥锁在默认情况下不是。) pthread_cond_signal()实际上只是对pthread_cond_broadcast()的优化。使用广播,直到你得到你的比赛条件了 在启动线程之前,应该在main的顶部初始化所有互斥锁和condvar。这里可能没有错误,但不会造成伤害,而且可能会有所帮助 如果在短期内将所有内容减少到一个互斥锁和一个condvar,您可能会做得更好。看起来您正在尝试对所有内容进行细粒度锁定,但除非您对锁的顺序非常小心,否则您将遇到竞争条件和死锁 实际上,只有一种模式/模板可用于互斥体和condvar:
pthread_mutex_lock(...);
// optionally wait for something to be true
while (!some_condition) {
pthread_cond_wait(...);
}
// make changes for how things should now be
shared_variable = new_value;
// optionally notify the rest of the world of your change
pthread_cond_broadcast(...);
pthread_mutex_unlock(...);
typedef struct {
pthread_mutex_t mutex;
pthread_cond_t condvar;
unsigned long count;
} semaphore_t;
void semaphore_init (semaphore_t* sem, unsigned long count) {
pthread_mutex_init(&sem->mutex, 0);
pthread_cond_init(&sem->condvar, 0);
pthread_mutex_lock(&sem->mutex);
sem->count = count;
pthread_mutex_unlock(&sem->mutex);
}
void semaphore_incr (semaphore_t* sem) {
pthread_mutex_lock(&sem->mutex);
sem->count++;
pthread_cond_broadcast(&sem->condvar);
pthread_mutex_unlock(&sem->mutex);
}
void semaphore_decr (semaphore_t* sem) {
pthread_mutex_lock(&sem->mutex);
while (sem->count == 0) {
pthread_cond_wait(&sem->condvar, &sem->mutex);
}
sem->count--;
pthread_mutex_unlock(&sem->mutex);
}
如果只有一个互斥体和condvar,那么应该尝试使所有同步块都像这样。如果您不需要等待,您可以省略while(…)/wait内容,如果没有其他线程关心您所做的更改,您可以省略广播,但是如果您的代码在每个同步块上的外观与此不同,则可能是一个bug。我认为您更适合使用信号量。以下是信号量在互斥体和condvar方面的实现:
pthread_mutex_lock(...);
// optionally wait for something to be true
while (!some_condition) {
pthread_cond_wait(...);
}
// make changes for how things should now be
shared_variable = new_value;
// optionally notify the rest of the world of your change
pthread_cond_broadcast(...);
pthread_mutex_unlock(...);
typedef struct {
pthread_mutex_t mutex;
pthread_cond_t condvar;
unsigned long count;
} semaphore_t;
void semaphore_init (semaphore_t* sem, unsigned long count) {
pthread_mutex_init(&sem->mutex, 0);
pthread_cond_init(&sem->condvar, 0);
pthread_mutex_lock(&sem->mutex);
sem->count = count;
pthread_mutex_unlock(&sem->mutex);
}
void semaphore_incr (semaphore_t* sem) {
pthread_mutex_lock(&sem->mutex);
sem->count++;
pthread_cond_broadcast(&sem->condvar);
pthread_mutex_unlock(&sem->mutex);
}
void semaphore_decr (semaphore_t* sem) {
pthread_mutex_lock(&sem->mutex);
while (sem->count == 0) {
pthread_cond_wait(&sem->condvar, &sem->mutex);
}
sem->count--;
pthread_mutex_unlock(&sem->mutex);
}
也许如果你用信号量来实现你的解决方案,你会得到你想要的结果。首先,xscott是正确的,你不正确地使用了互斥体。不管它是否在短时间内起作用,它仍然是错误的,而且可能只是因为纯粹的偶然性才起作用 我认为最好的方法不是逐行检查代码,而是从第一原则构建设计。我会这样描述我认为您所追求的基本算法:
visitor {
sleep
join end of visitor queue
wait until at head of visitor queue
wait until there is a car free
remove car from car queue
remove self from visitor queue
occupy car
wait until not in car anymore
}
car {
join end of car queue
wait until occupied
sleep
eject visitor from car
}
pthread_mutex_t c_mutex = PTHREAD_MUTEX_INITIALIZER; /* mutex protecting c_line and cars_out */
pthread_mutex_t v_mutex = PTHREAD_MUTEX_INITIALIZER; /* mutex protecting v_line */
pthread_cond_t c_cond[CARS]; /* condition variables for waiting for c_state[i] to change from VISITORS to another value */
pthread_cond_t v_cond[VISITORS]; /* condition variables for visitor waiting to be first in line or ejected from a car */
pthread_cond_t v_car_cond = PTHREAD_COND_INITIALIZER; /* Condition variable for a visitor first in line, but waiting for a car. */
pthread_mutex_t sc[CARS]; /* one mutex per car, sc[i] protects c_state[i] */
int main(){
int i;
void *status;
pthread_t c[CARS];
pthread_t v[VISITORS];
srand(time(NULL));
printf("Jurassic Park is open, cars are being prepped for passengers\n");
for (i = 0; i < CARS; i++){
pthread_mutex_init(&sc[i], NULL);
pthread_cond_init(&c_cond[i], NULL);
}
for (i = 0; i < VISITORS; i++){
pthread_cond_init(&v_cond[i], NULL);
}
for (i = 0; i < CARS; i++){
c_state[i] = VISITORS;
pthread_create(&c[i], NULL, car, (void *)i);
}
for (i = 0; i < VISITORS; i++){
pthread_create(&v[i], NULL, visitor, (void *)i);
}
for (i = 0; i < VISITORS; i++){
pthread_join(v[i], &status);
}
museum_empty++;
printf("All visitors have left Jurassic Park\n");
for (i = 0; i < CARS; i++){
pthread_mutex_lock(&sc[i]);
c_state[i] = -1;
pthread_cond_signal(&c_cond[i]);
pthread_mutex_unlock(&sc[i]);
}
for (i = 0; i < CARS; i++){
pthread_join(c[i], &status);
}
printf("Jurrasic Park is closed, all cars have been parked\n");
pthread_exit(NULL);
return 0;
}
void *car(void *i)
{
int c_id = (int) i;
int v_id;
while (museum_empty != 1) {
/* join end of car queue */
pthread_mutex_lock(&c_mutex);
c_line[c_id] = set_car_place_in_line();
if (c_line[c_id] == 1)
pthread_cond_signal(&v_car_cond);
printf("Car %d is ready for a passenger and is %d in line %d of %d cars are out\n", c_id, c_line[c_id], cars_out, CARS);
pthread_mutex_unlock(&c_mutex);
/* wait until occupied */
pthread_mutex_lock(&sc[c_id]);
while ((v_id = c_state[c_id]) == VISITORS) {
pthread_cond_wait(&c_cond[c_id], &sc[c_id]);
}
pthread_mutex_unlock(&sc[c_id]);
if(museum_empty == 1){
break;
}
pthread_mutex_lock(&c_mutex);
cars_out++;
printf("Visitor %d is riding in car %d %d of %d cars are out --\n", v_id, c_id, cars_out, CARS);
pthread_mutex_unlock(&c_mutex);
/* visitor is on car ride for random amount of time */
sleep(rand()%5);
printf("Visitor %d is done riding in car %d\n", v_id, c_id);
/* eject visitor from car */
pthread_mutex_lock(&sc[c_id]);
c_state[c_id] = VISITORS;
pthread_cond_signal(&v_cond[v_id]);
pthread_mutex_unlock(&sc[c_id]);
pthread_mutex_lock(&c_mutex);
cars_out--;
pthread_mutex_unlock(&c_mutex);
}
return NULL;
}
void *visitor(void *i)
{
int v_id = (int) i;
int next_v;
int j = 0;
int car;
while (j < RIDES) {
if (j == 0) {
printf("Visitor %d entered museum and is wandering around\n", v_id);
} else {
printf("Visitor %d got back from ride and is wandering around\n", v_id);
}
sleep(rand()%3); /* visitor is wandering */
/* join end of visitor queue */
pthread_mutex_lock(&v_mutex);
v_line[v_id] = set_visitor_place_in_line();
printf("Visitor %d is %d in line for a ride\n", v_id, v_line[v_id]);
/* wait until first in line */
while (v_line[v_id] != 1) {
pthread_cond_wait(&v_cond[v_id], &v_mutex);
}
pthread_mutex_unlock(&v_mutex);
/* wait until there is a car free */
pthread_mutex_lock(&c_mutex);
while ((car = get_next_car()) == CARS) {
pthread_cond_wait(&v_car_cond, &c_mutex);
}
/* remove car from car queue */
move_car_line();
pthread_mutex_unlock(&c_mutex);
/* remove self from visitor queue */
pthread_mutex_lock(&v_mutex);
move_passenger_line();
next_v = get_next_passenger();
if (next_v < VISITORS)
pthread_cond_signal(&v_cond[next_v]);
pthread_mutex_unlock(&v_mutex);
/* occupy car */
pthread_mutex_lock(&sc[car]);
c_state[car] = v_id;
pthread_cond_signal(&c_cond[car]);
/* wait until not in car anymore */
/* NOTE: This test must be against v_id and *not* VISITORS, because the car could have been
* subsequently occupied by another visitor before we are woken. */
while(c_state[car] == v_id) {
pthread_cond_wait(&v_cond[v_id], &sc[car]);
}
pthread_mutex_unlock(&sc[car]);
j++;
}
printf("Visitor %d leaving museum.\n", v_id);
return NULL;
}
请注意,我没有明确标记唤醒点,只是标记等待。这是最好的方法——找出需要在哪里等待某些东西改变状态,然后只要在状态改变时放置唤醒(向条件变量发送信号)
下一步是确定需要使用互斥锁保护的主要共享数据。我明白了:
- The visitor queue;
- The car queue;
- The status of each car.
因此,最精细的方法是为访客队列设置一个互斥体(我们可以使用您的v_互斥体
),为汽车队列设置一个互斥体(c_互斥体
),为每辆汽车设置一个互斥体(sc[CARS]
)。或者,您可以使用c_mutex
来保护车辆队列和每辆车的状态;或者只需使用v_互斥
来保护一切。但是为了学习,我们将使用更复杂的方法
下一步是确定条件变量有用的等待点。对于等待,直到在访客队列的最前面
,您的每访客条件变量(v_cond
)将正常;对于等待,直到有无车时
最容易添加另一个条件变量(v\u car\u cond
)。对于等待,直到有人占用每辆车的状况变量c_cond
都是合适的。对于等待,直到不再在车内
可以使用v_cond
或c_cond
,因为此时车辆和访客之间存在一对一的关系。不需要v_cond2
我们现在可以用pthreads原语重新编写上面的伪代码。在大多数情况下,这与您已经拥有的非常接近-s