使用条件变量的Pthread程序死锁

该计划正在努力实现的目标:

该程序应该同步“访客”和“汽车”的几个线程。 游客们随意漫步,直到他们决定乘车。 如果他们是第一次乘坐汽车并且有车可以乘坐,那么他们必须等到他们第一次排队或者车回来。 如果没有游客排队,那么汽车会等待,直到游客想要乘车。

更多背景信息:

我使用条件变量重新构建我的线程同步程序,如此处接受的答案中所建议的那样。 我知道我在正确的轨道上,但由于某些原因,我的程序仍然陷入僵局,对于我的生活,我无法弄清楚为什么。 除非我给你代码,否则我不知道你怎么能帮助我,所以这里是:

问题:

1.)一小段后死锁

2.)有时候一位游客首先排队买车,但从不上车。

解决方案:

我的代码中有太多的错误…我想我会修复一个,我经常(不经意间)引入另一个。 我一直在删除程序的function,直到我消除了所有的错误,然后以一种不会使我的程序死锁的方式逐个构建function。 谢谢大家的建议。

首先,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 } 

请注意,我没有明确标记唤醒点 – 只是等待。 这是最好的方法 – 找出你需要等待某些东西改变状态的地方,然后只需要在状态发生变化时放置唤醒(发出条件变量信号)。

下一步是确定需要由互斥锁保护的主要共享数据。 我知道了:

 - The visitor queue; - The car queue; - The status of each car. 

因此,最细粒度的方法是为访问者队列设置一个互斥锁(我们可以使用你的v_mutex ),一个用于汽车队列( c_mutex ),每个汽车用一个c_mutexsc[CARS] )。 或者,您可以使用c_mutex来保护汽车队列和每辆汽车的状态; 或者只是使用v_mutex来保护一切。 但是为了学习,我们将采用更复杂的方法。

下一步是确定条件变量有用的等待点。 wait until at head of visitor queue ,您的每个访客条件变量( v_cond )将是正常的; wait until there is a car free ,最容易添加另一个条件变量( v_car_cond )。 wait until occupied每车的条件变量c_cond是合适的。 为了wait until not in car anymore可以使用v_condc_cond ,因为此时汽车和访客之间存在1对1的关系。 不需要v_cond2

我们现在可以根据pthreads原语重写上面的伪代码。 在大多数情况下,这非常接近你已经拥有的 – 所以你肯定是在正确的轨道上。 首先是访客:

  /* 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]); pthread_mutex_unlock(&v_mutex); /* wait until first in line */ pthread_mutex_lock(&v_mutex); 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); } pthread_mutex_unlock(&c_mutex); /* remove car from car queue */ pthread_mutex_lock(&c_mutex); move_car_line(); /* NOTE: We do not need to signal v_car_cond here, because only the first * visitor in line can be waiting there, and we are still the first visitor * in line at this point. */ 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]); pthread_mutex_unlock(&sc[car]); /* wait until not in car anymore */ pthread_mutex_lock(&sc[car]); while(c_state[car] == v_id) { pthread_cond_wait(&v_cond[v_id], &sc[car]); } pthread_mutex_unlock(&sc[car]); 

二,车:

  /* 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); 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]); /* visitor is on car ride for random amount of time */ sleep(rand()%5); /* 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_unlock()紧接着是同一个互斥锁的pthread_mutex_lock() ,就可以删除解锁/锁定对。

PS:

你不应该担心你的汽车以“错误的顺序”加入汽车排队 - 无论如何他们会在公园周围徘徊时出现故障。 如果您真的关心这一点,请在启动任何汽车线程之前将汽车放入主线程的队列中,并更改汽车代码,以便在主循环结束时将其重新添加到队列中。


使用这种方法的整体代码,省略了你的全局变量和辅助函数,这很好,如下所示:

 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; } 

我希望这是有帮助的...

你有很多代码,所以不太可能有人为你找到所有的bug。 但是,一些评论:

互斥体不是信号量。 main()中的几个for循环正在解锁尚未锁定的互斥锁。 这几乎肯定是一个错误。 从概念上讲,互斥锁可以用信号量实现,你可以使用互斥锁和condvar实现一个信号量,但是你正在解锁未锁定的互斥锁,这是不正确的。

每个线程都应该锁定一个互斥锁,做一些工作,然后解锁它。 线程不应解锁已被另一个线程锁定的互斥锁,或抢先解锁未锁定的互斥锁。 如果这种方法有效,那么在您当前使用的实现中这是一个怪癖,并且不能移植到其他实现中。

你在main中的第二个for循环连续两次锁定相同的互斥锁。 你是否在代码中超越了这一点? 因为你正在循环,所以锁定它比解锁它更多。 如果你的代码停在这里,我不会感到惊讶。 (有时互斥锁可以递归,但默认情况下不会使用pthread互斥锁。)

pthread_cond_signal()实际上只是对pthread_cond_broadcast()的优化。 使用广播,直到你的竞争条件得到解决。

在启动线程之前,应该在main的顶部初始化所有互斥锁和condvar。 你可能在这里没有错误,但它不会受到伤害,它可能有所帮助。

如果在短期内将所有内容减少到单个互斥锁和单个condvar,则可能会做得更好。 看起来你正试图用一切来做细粒度锁定,但除非你真的小心你的锁定顺序,否则你将会遇到竞争条件和死锁。

实际上,只有一个模式/模板应该与互斥锁和condvars一起使用:

 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(...); 

如果您有一个互斥锁和condvar,您应该尝试使所有同步块看起来像这样。 如果你不需要等待,可以省略while(…)/ wait等东西,如果没有其他线程关心你所做的更改,你可以省略广播,但如果你的代码没有大致看就像每个同步块一样,它可能是一个bug。

我觉得你对信号量感觉更舒服。 以下是互斥锁和condvars的信号量实现:

 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); } 

也许如果你用信号量来实现你的解决方案,你将得到你想要的结果。