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

C 使用条件变量的Pthread程序死锁,c,pthreads,deadlock,C,Pthreads,Deadlock,此程序试图实现的目标: 这个程序应该同步“访客”和“汽车”的几个线程。游客们会在一段随机的时间里四处游荡,直到他们决定去坐车。如果他们是第一个排队坐车的人,并且有一辆车可供他们乘坐,否则他们必须等到第一个排队或者有车回来。如果没有游客排队,汽车会按顺序等待,直到有游客想乘车 更多背景信息: 我使用接受的答案中建议的条件变量重新编写了线程同步程序。我知道我在正确的轨道上,但我的程序仍然因为某些原因而死锁,而对于我的一生,我不知道为什么。除非我给你密码,否则我不知道你能帮我什么,所以这里是: 问题:

此程序试图实现的目标:

这个程序应该同步“访客”和“汽车”的几个线程。游客们会在一段随机的时间里四处游荡,直到他们决定去坐车。如果他们是第一个排队坐车的人,并且有一辆车可供他们乘坐,否则他们必须等到第一个排队或者有车回来。如果没有游客排队,汽车会按顺序等待,直到有游客想乘车

更多背景信息:

我使用接受的答案中建议的条件变量重新编写了线程同步程序。我知道我在正确的轨道上,但我的程序仍然因为某些原因而死锁,而对于我的一生,我不知道为什么。除非我给你密码,否则我不知道你能帮我什么,所以这里是:

问题:

1.)短位后出现死锁

2.)有时,游客排在排队等候汽车的第一位,但从未上车

解决方案:


我的代码中有太多的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