Pthread程序使用条件变量死锁

时间:2010-12-07 21:29:01

标签: c pthreads deadlock

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

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

更多背景信息:

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

问题:

1。)短暂的死锁后

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

解决方案:

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

3 个答案:

答案 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 anymorev_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);
}

也许如果您根据信号量实施解决方案,您将获得您所追求的结果。