pthread_cond_wait在fifo循环队列中死锁

时间:2014-10-11 05:22:10

标签: c multithreading algorithm thread-safety pthreads

我的代码仅用于一个生产者 - 一个消费者的情况。

这是我的测试代码:

static void *afunc(void * arg) {
    Queue* q  = arg;
    for(int i= 0; i< 100000; i++) {
        *queue_pull(q) = i; //get one element space
        queue_push(q);      //increase the write pointer
    }
    return NULL;
}
static void *bfunc(void * arg) {
    Queue* q  = arg;
    for(;;) {
        int *i = queue_fetch(q); //get the first element in queue
        printf("%d\n", *i);
        queue_pop(q);   //increase the read pointer
    }
}
int main() {
    Queue queue;
    pthread_t a, b;
    queue_init(&queue);
    pthread_create(&a, NULL, afunc, &queue);
    pthread_create(&b, NULL, bfunc, &queue);

    sleep(100000);
    return 0;
}

这里是循环队列的实现

#define MAX_QUEUE_SIZE 3
typedef struct Queue{ 
    int data[MAX_QUEUE_SIZE] ; 
    int read,write; 
    pthread_mutex_t mutex, mutex2; 
    pthread_cond_t not_empty, not_full; 
}Queue; 
int queue_init(Queue *queue) { 
    memset(queue, 0, sizeof(Queue)); 
    pthread_mutex_init(&queue->mutex, NULL); 
    pthread_cond_init(&queue->not_empty, NULL); 
    pthread_mutex_init(&queue->mutex2, NULL); 
    pthread_cond_init(&queue->not_full, NULL); 
    return 0; 
} 
int* queue_fetch(Queue *queue) { 
    int* ret; 
    if (queue->read == queue->write) {
        pthread_mutex_lock(&queue->mutex); 
        pthread_cond_wait(&queue->not_empty, &queue->mutex); 
        pthread_mutex_unlock(&queue->mutex); 
    } 
    ret = &(queue->data[queue->read]); 
    return ret; 
} 
void queue_pop(Queue *queue) { 
    nx_atomic_set(queue->read, (queue->read+1)%MAX_QUEUE_SIZE); 
    pthread_cond_signal(&queue->not_full); 
} 
int*  queue_pull(Queue *queue) { 
    int* ret; 
    if ((queue->write+1)%MAX_QUEUE_SIZE == queue->read) { 
        pthread_mutex_lock(&queue->mutex2); 
        pthread_cond_wait(&queue->not_full, &queue->mutex2); 
        pthread_mutex_unlock(&queue->mutex2); 
    } 
    ret = &(queue->data[queue->write]); 
    return ret; 
} 
void queue_push(Queue *queue) { 
        nx_atomic_set(queue->write, (queue->write+1)%MAX_QUEUE_SIZE); 
        pthread_cond_signal(&queue->not_empty); 
} 
过了一会儿,似乎两个子线程将变成死锁..

编辑:我使用两个信号量,但它也有一些问题..它很漂亮 很奇怪,如果只是执行./main,它似乎很好,但如果我重定向到一个文件,如./main&gt; a.txt,然后是wc -l a.txt,结果不等于入队号..

int queue_init(Queue *queue) {
    memset(queue, 0, sizeof(Queue));
    pthread_mutex_init(&queue->mutex, NULL);
    sem_unlink("/not_empty");
    queue->not_empty = sem_open("/not_empty", O_CREAT, 644, 0);
    sem_unlink("/not_full");
    queue->not_full = sem_open("/not_full", O_CREAT, 644, MAX_QUEUE_SIZE);
    return 0;
}

int* queue_fetch(Queue *queue) {
    sem_wait(queue->not_empty);
    return &(queue->data[queue->read]);
}
void queue_pop(Queue *queue) {
    nx_atomic_set(queue->read, (queue->read+1)%MAX_QUEUE_SIZE);
    sem_post(queue->not_full);
}

int* queue_pull(Queue *queue) {
    sem_wait(queue->not_full);
    return  &(queue->data[queue->write]);
}
void queue_push(Queue *queue) {
    nx_atomic_set(queue->write, (queue->write+1)%MAX_QUEUE_SIZE);
    sem_post(queue->not_empty);
}

2 个答案:

答案 0 :(得分:1)

很可能你的一个线程在发出信号后等待一个条件发出信号,导致两个线程无限期地等待。

Pthreads条件变量不会保持信号 - 信号是一个瞬间动作。条件变量不用于确定是否等待 - 它只用于唤醒已经等待的线程;您需要一种不同的方法来确定是否等待,例如检查标志或某种测试条件。

通常情况下,您的信号如下:

  1. 锁定互斥锁
  2. 做您的更新,通常会让您的测试条件真实无误。 (例如,设置你的国旗)
  3. 致电pthread_cond_signal()pthread_cond_broadcast()
  4. 解锁互斥锁
  5. ...等待如下:

    1. 锁定互斥锁
    2. 循环,直到您的测试表达式为“真实”&#39; (例如,直到你的标志被设置),只有当测试为假时才调用pthread_cond_wait() (在循环内)。
    3. 循环结束后,当您的测试成功时,请完成您的工作。
    4. 解锁互斥锁
    5. 例如,信号可能是这样的:

      pthread_mutex_lock(&mtx);     /* 1: lock mutex */
        do_something_important();   /* 2: do your work... */
        ready_flag = 1;                /* ...and set the flag */
        pthread_cond_signal(&cond); /* 3: signal the condition (before unlocking) */
      pthread_mutex_unlock(&mtx);   /* 4: unlock mutex */
      

      等待可能是这样的:

      pthread_mutex_lock(&mtx);           /* 1: lock mutex */
        while (ready_flag == 0)           /* 2: Loop until flag is set... */
          pthread_cond_wait(&cond, &mtx);    /* ...waiting when it isn't */
        do_something_else();              /* 3: Do your work... */
        ready_flag = 0;                      /* ...and clear the flag if it's all done */
      pthread_mutex_unlock(&mtx);         /* 4: unlock mutex */
      

      服务员不会以这种方式错过这种情况,因为互斥锁确保服务员的测试和等待以及信号器的设置和信号不能同时发生。


      queue_fetch()功能的这一部分:

      if (queue->read == queue->write) {
          pthread_mutex_lock(&queue->mutex); 
          pthread_cond_wait(&queue->not_empty, &queue->mutex); 
          pthread_mutex_unlock(&queue->mutex); 
      } 
      ret = &(queue->data[queue->read]); 
      

      ..可能会改写如下:

      pthread_mutex_lock(&queue->mutex);
        while (queue->read == queue->write)
            pthread_cond_wait(&queue->not_empty, &queue->mutex);
        ret = &(queue->data[queue->read]);
      pthread_mutex_unlock(&queue->mutex);
      

      ...其中:

      1. 互斥锁的锁定/解锁在if左右移动,因此在评估测试表达式时保持互斥锁,并且在状态等待开始之前一直保持互斥锁
      2. 如果条件等待过早中断,if会更改为while
      3. 使用互斥锁
      4. 访问queue->readqueue->write

        将对queue_pull()进行类似的更改。

        至于信令代码,queue_pop()的以下部分:

        nx_atomic_set(queue->read, (queue->read+1)%MAX_QUEUE_SIZE); 
        pthread_cond_signal(&queue->not_full); 
        

        ..可能会更改为:

        pthread_mutex_lock(&queue->mutex);
          queue->read = (queue->read + 1) % MAX_QUEUE_SIZE;
          pthread_cond_signal(&queue->not_full);
        pthread_mutex_unlock(&queue->mutex);
        

        ...其中:

        1. 在发出状态信号时保持互斥锁(这可确保在服务员决定是否等待和实际开始等待之间不会发出信号,因为服务员会在该间隔期间保持互斥锁)
        2. 在更改queue->read时保留互斥锁,而不是使用nx_atomic_set(),因为无论如何都需要使用互斥锁
        3. 将对queue_push()进行类似的更改。


          此外,您应该只使用一个互斥锁(以便在访问readwrite时始终保持相同的互斥锁),并且在while循环添加到条件后等待那里使用多个条件变量的一些令人信服的理由。如果切换到单个条件变量,只需在完成等待后再次发出条件:

          pthread_mutex_lock(&queue->mutex);
            while (queue->read == queue->write) {
                pthread_cond_wait(&queue->cond, &queue->mutex);
                pthread_cond_signal(&queue->cond); /* <-- signal next waiter, if any */
            }
            ret = &(queue->data[queue->read]);
          pthread_mutex_unlock(&queue->mutex);
          

答案 1 :(得分:1)

您正在操作互斥锁之外的队列状态,这本身就是一种争吵。

我建议使用一个互斥锁,但每当你更改或测试读取和放大时都要使用它。写指标。这也意味着您不需要原子集。