该计划正在努力实现的目标:
该程序应该同步“访客”和“汽车”的几个线程。游客们随意漫步,直到他们决定乘车。如果他们是第一次乘坐汽车并且有车可以乘坐,那么他们必须等到他们第一次排队或者车回来。如果没有排队的游客,车辆会按顺序排队等候,直到游客想要乘车。
更多背景信息:
我使用条件变量重新构建了线程同步程序,如接受的答案here中所建议的那样。我知道我在正确的轨道上,但由于某些原因,我的程序仍然陷入僵局,对于我的生活,我无法弄清楚原因。除非我给你代码,否则我不知道你怎么能帮助我,所以这就是:
问题:
1。)短暂的死锁后
2。)有时候一位游客首先排队买车,但从不上车。
解决方案:
我的代码中存在太多错误......我认为我会修复一个错误,我经常(无意中)引入另一个错误。我一直在删除程序的功能,直到我消除了所有的错误,然后以一种不会使我的程序死锁的方式逐个构建功能。谢谢大家的建议。
答案 0 :(得分:5)
首先,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
),每个汽车用一个互联网({{{ 1}})。或者,您可以使用sc[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_cond
,因为此时汽车和访问者之间存在1对1的关系。不需要c_cond
。
我们现在可以根据pthreads原语重写上面的伪代码。在大多数情况下,这是非常接近你已经拥有的 - 所以你肯定是在正确的轨道上。首先是访客:
v_cond2
第二,汽车:
/* 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()
相同的互斥锁,就可以删除解锁/锁定对。
<强> PS:强>
你不应该担心你的汽车以“错误的顺序”加入汽车队列 - 无论如何他们会在公园周围徘徊时出现故障。如果您真的关心这一点,请在启动任何汽车线程之前将汽车放入主线程的队列中,并更改汽车代码,以便在主循环结束时将其重新添加到队列中。
使用这种方法的整体代码,省略了你的全局变量和辅助函数,这很好,如下所示:
pthread_mutex_lock()
我希望这有用......
答案 1 :(得分:4)
你有很多代码,因此不太可能有人为你找到所有的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等东西,如果没有其他线程关心你所做的更改,你可以省略广播,但如果你的代码没有大致看就像每个同步块一样,它可能是一个错误。
答案 2 :(得分:1)
我觉得你对信号量感觉更舒服。这是信号量和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);
}
也许如果您根据信号量实施解决方案,您将获得您所追求的结果。