4

pthreadsプログラミングに慣れてきました。次のコードは、データがグローバル リストから配置/フェッチされる単純なプロデューサー/コンシューマー デザインです。問題は、コンシューマー関数のデータ ポインターが 2 回解放されようとしていることです。さらに、ループの先頭にa を追加するprintf()と、すべて問題ないように見えます...何が間違っていますか? キーワードの誤用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

ステップ11write_ptrを確認してください。プロデューサースレッドとは関係なく解放されたにもかかわらず、まだd0です。consumeが無効にならないようにする必要がありwrite_ptrます。

二重にリンクされたリストを使用すると、これらの問題のいくつかを回避できます(リーダーとライターは別々の端から作業するため)。

主要:

  • センチネルリンパ節HEADを作成しTAIL、リンクHEADし、TAIL
  • スポーンプロデューサー
  • スポーンコンシューマー

プロデューサー:

  • ロック
  • ノードを作成します
  • リンクHEAD->next->prevnode
  • リンクnode->nextHEAD->next
  • リンクHEAD->nextnode
  • リンクnode->prevHEAD
  • ロックを解除する
  • 信号

消費者:

  • ロック
  • 待つTAIL->prev != HEADdo { 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;

リストを正しく作成しているとは思いません。実際、なぜ2つのリストがあるのか​​わかりません。とにかく...

新しいデータをリストの先頭に追加したいと思います。それ以外の場合は、テールポインターが必要になるか、毎回リストの最後まで追跡する必要があります。

これを行うには、現在のリストヘッドをデータの次のアイテムとして追加する必要があります。次のようになります。

data->next = write_ptr;
write_ptr = data;

write_ptrでNULLチェックを行う必要はありません。

于 2013-01-28T20:30:32.897 に答える
1

更新: Billy ONeal が指摘したように、pthread 関数は必要なメモリ バリアを提供するため、pthread ロックによって保護されている限り、揮発性を宣言する必要はありません。(詳細については、この質問を参照してください:変数を pthread ミューテックスで保護すると、変数もキャッシュされないことが保証されますか? )

しかし、いくつかの奇妙な動作について別の説明を得ました: プロデューサーが作成するリンクされたリストが壊れています:write_ptrが NULL ではないと仮定し、動作を観察します:

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

write_ptr が以前に割り当てられたインスタンス AAnext を指しているとします。インスタンス B を新たに割り当て、すべてを NULL に設定します (したがって、B.next は NULL です)。

行 1 は true と評価されるため、行 2 が実行されます。A.next は現在 B を指しています。3 行目を実行した後、write_ptr は B を指しています。B.next は NULL => A が失われ、メモリ リークが発生します。

しかし今のところ、libc が double free について不平を言う理由がわかりません。

于 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メモリ マップされたハードウェア リソースにアクセスする場合にのみ必要であり、同期には使用しないでください。それが行うのは、データを不必要にメモリに強制することだけです。

また、もう 1 つの問題があります。プロデューサがコンシューマよりも高速な場合、キューのサイズを制限してプロデューサにコンシューマを待機させない限り、最終的にメモリ不足になります。小さいキューは応答性が高く、大きいキューを使用する唯一の理由は、プロデューサーでの摂動をスムーズにすることです。

于 2013-01-28T20:44:10.317 に答える