4

我开始熟悉pthreads编程;以下代码是一个简单的生产者/消费者设计,其中数据从全局列表中放置/获取。问题是:消费者函数中的数据指针被尝试释放两次。此外,如果我printf()在循环的开头添加 a ,一切似乎都很好......我做错了什么?我怀疑关键字的滥用volatile,或者缓存隐藏的东西......除非它只是一个设计问题(可能是:p)。

感谢您的见解。

注意:malloc()/free()在我的系统上是线程安全的。我正在使用它进行编译$ gcc -pthread -O0,我猜这应该可以减少由于滥用volatile. 最后,在这段代码片段中,我们并不关心内存不足(如果生产者多于消费者)。

编辑:将代码更改为单个列表头。

#include <pthread.h>
#include <stdlib.h>
#include <string.h>

pthread_mutex_t lock;
pthread_cond_t new_data;

struct data {
  int i;
  struct data *next;
};

struct data *list_head = NULL;

void *consume(void *arg)
{
  struct data *data;

  while (1) {
    pthread_mutex_lock(&lock);
    while (list_head == NULL) {
      pthread_cond_wait(&new_data, &lock);
    }
    data = list_head;
    list_head = list_head->next;
    pthread_mutex_unlock(&lock);
    free(data);
  }

  return NULL;
}

void *produce(void *arg)
{
  struct data *data;

  while (1) {
    data = malloc(sizeof(struct data));
    pthread_mutex_lock(&lock);
    data->next = list_head;
    list_head = data;
    pthread_mutex_unlock(&lock);
    pthread_cond_signal(&new_data);
  }

  return NULL;
}

int main()
{
  pthread_t tid[2];
  int i;

  pthread_mutex_init(&lock, NULL);
  pthread_cond_init(&new_data, NULL);
  pthread_create(&tid[0], NULL, consume, NULL);
  pthread_create(&tid[1], NULL, produce, NULL);
  for (i = 0; i < 2; i++) {
    pthread_join(tid[i], NULL);
  }
}

和输出:

$ ./a.out 
*** glibc detected *** ./a.out: double free or corruption (fasttop): 0x00007f5870109000 ***
4

4 回答 4

4

考虑以下场景:

  1. 产生 -> 获得锁
  2. 消费 -> 等待锁
  3. 产生 -> 分配 d0,write_ptr = d0,read_ptr = d0,信号,解锁
  4. 消费 -> 获得锁
  5. 产生 -> 等待锁
  6. 消费 -> 满足条件
  7. 消费 -> 数据 = d0,read_ptr = NULL,解锁
  8. 消耗 -> 释放 d0
  9. 产生 -> 获得锁,分配 d1
  10. 消费 -> 等待锁
  11. 产生 ->write_ptr != null所以write_ptr->next = d1
  12. 产生 ->read_ptr == null所以read_ptr = d1

看看第 11 步write_ptr,它仍然是 d0,即使它是独立于生产者线程而释放的。您需要确保consume不会使write_ptr.

双向链表可以让您避免其中的一些困难(因为读者和作者从不同的端工作)。

主要的:

  • 创建哨兵节点HEADTAIL,链接HEADTAIL
  • 产卵生产者
  • 产生消费者

制片人:

  • 创建节点
  • 链接HEAD->next->prevnode
  • 链接node->nextHEAD->next
  • 链接HEAD->nextnode
  • 链接node->prevHEAD
  • 开锁
  • 信号

消费者:

  • 等待TAIL->prev != HEAD( do { pthread_cond_wait } while (condition);)
  • data = TAIL->prev
  • 链接TAIL->prevdata->prev
  • 链接TAIL->prev->nextTAIL
  • 开锁
  • 采用data
于 2013-01-28T20:30:18.987 回答
4

我相信您的问题出在以下几行:

if (NULL != write_ptr)
  write_ptr->next = data;
write_ptr = data;
if (NULL == read_ptr)
  read_ptr = data;

我认为您没有正确构建您的列表。事实上,我不明白你为什么有两个列表。但无论如何...

我假设您想将新数据添加到列表的开头。否则,您将需要一个尾指针,或者您每次都需要追到列表的末尾。

为此,您需要将当前列表头添加为数据的下一项。它应该看起来像:

data->next = write_ptr;
write_ptr = data;

不需要对 write_ptr 进行 NULL 检查。

于 2013-01-28T20:30:32.897 回答
1

更新:正如 Billy ONeal 所指出的,pthread 函数提供了所需的内存屏障,因此只要受到 pthread 锁的保护,就没有必要声明任何 volatile 。(有关详细信息,请参阅此问题:使用 pthread 互斥锁保护变量是否保证它也不会被缓存?

但是我对一些奇怪的行为得到了另一种解释:生产者创建的链接列表已损坏:假设write_ptr不是 NULL 并观察行为:

/* 1 */ if (NULL != write_ptr)
/* 2 */   write_ptr->next = data;
/* 3 */ write_ptr = data;

假设 write_ptr 指向先前分配的实例 AAnext 为 NULL。我们新分配了一个实例 B 并将所有内容设置为 NULL(因此:B.next 为 NULL)。

第 1 行评估为真,因此执行第 2 行。A.next 现在指向 B。执行第 3 行后,write_ptr 指向 B B.next 为 NULL => A 丢失导致内存泄漏。

但是现在我不明白为什么 libc 抱怨双重免费。

于 2013-01-28T20:12:40.773 回答
1

错误在行中

if (NULL != write_ptr)
   write_ptr->next = data;
write_ptr = data;

这应该是: if (NULL != write_ptr) data->next = write_ptr; write_ptr = 数据;

出于调试目的,还要确保您将预期值从队列中取出。

也不需要volatile变量。由于您的队列受互斥锁保护,操作系统将确保队列访问是原子的。volatile仅在访问内存映射的硬件资源时才需要,并且绝不能用于同步。它所做的只是不必要地将数据强制存储到内存中。

还有另一个问题。如果你的生产者比你的消费者快,你最终会耗尽内存,除非你限制队列的大小并强制生产者等待消费者。较小的队列响应速度更快,选择较大队列的唯一原因是消除生产者中的干扰。

于 2013-01-28T20:44:10.317 回答