【发布时间】:2019-11-29 19:26:36
【问题描述】:
我自己承担了用 C 开发并发通用哈希表的任务。
hash_table.h的相关内容:
typedef struct list_node {
void * data;
struct list_node * next;
} list_node_t;
typedef struct hash_table {
int max_size;
int count;
list_node_t * * elements;
pthread_rwlock_t * locks;
pthread_rwlock_t global_table_lock;
hash_table_compare_function compare;
hash_table_hash_function hash;
} hash_table_t;
hash_table.c的相关内容:
#define LOCK_RD(lock) pthread_rwlock_rdlock(&lock);
#define LOCK_WR(lock) pthread_rwlock_wrlock(&lock);
#define UNLOCK(lock) pthread_rwlock_unlock(&lock);
bool
hash_table_remove(hash_table_t * table, void * element)
{
int hash_value = table->hash(element);
list_node_t * node, * prev;
LOCK_WR(table->locks[hash_value]);
node = table->elements[hash_value];
prev = NULL;
while (node) {
if (!table->compare(node->data, element)) {
// value is first item in the list
if (node == table->elements[hash_value]) {
table->elements[hash_value] = node->next;
free(node);
UNLOCK(table->locks[hash_value]);
LOCK_WR(table->global_table_lock);
table->count--;
UNLOCK(table->global_table_lock);
return true;
} else {
// link previous node with one after current
prev->next = node->next;
free(node);
UNLOCK(table->locks[hash_value]);
LOCK_WR(table->global_table_lock);
table->count--;
UNLOCK(table->global_table_lock);
return true;
}
}
prev = node;
node = node->next;
}
UNLOCK(table->locks[hash_value]);
return false;
}
我写了一个使用字符串的测试用例,相关代码如下:
#include "hashtable.h"
#define NUM_THREADS 2
#define NUM_STRINGS 154560
#define NUM_LOOKUPS 10000
void *
do_work(void * data)
{
int thread_id = *(int*)data;
// write "threadX.txt" to filename, where X is the given thread id
char filename[64];
strcpy(filename, "thread");
char thread_id_str[4];
sprintf(thread_id_str, "%d", thread_id);
strcat(filename, thread_id_str);
strcat(filename, ".txt");
FILE * file = fopen(filename, "r");
char buffer[128];
int i, num_str_per_thread = NUM_STRINGS / NUM_THREADS;
char * str_array[num_str_per_thread];
for (i = 0; i < num_str_per_thread; i++) {
fgets(buffer, 128, file);
str_array[i] = calloc((strlen(buffer) + 1), sizeof(char));
strcpy(str_array[i], buffer);
}
fclose(file);
for (i = 0; i < num_str_per_thread; i++)
hash_table_insert(table, str_array[i]);
for (i = 0; i < NUM_LOOKUPS; i++)
hash_table_contains(table, str_array[rand() % num_str_per_thread]);
for (i = 0; i < num_str_per_thread / 2; i++)
hash_table_remove(table, str_array[rand() % num_str_per_thread]);
//sleep(2); NOTE: no read errors reported if I leave this sleep() here.
for (i = 0; i < num_str_per_thread; i++)
if (str_array[i])
free(str_array[i]);
return NULL;
}
void
create_workers()
{
pthread_t threads[NUM_THREADS];
int ids[NUM_THREADS];
int i;
for (i = 0; i < NUM_THREADS; i++)
ids[i] = i + 1;
for (i = 0; i < NUM_THREADS; i++)
pthread_create(&threads[i], NULL, do_work, (void*)&ids[i]);
for (i = 0; i < NUM_THREADS; i++)
pthread_join(threads[i], NULL);
}
测试用例应该如下工作:有两个文件thread1.txt和thread2.txt,每个文件都包含我事先生成的unique字符串。我创建了两个线程,每个线程将从一个文件中读取并将每个字符串存储在一个名为str_array 的字符串数组中。然后他们会将所有这些字符串插入哈希表并执行随机搜索(hash_table_contains)和删除(hash_table_remove)。然后,每个人都会释放他们各自的字符串数组。但是,当我运行这个测试用例时,Valgrind 会报告以下内容:
请注意没有内存泄漏。我从这些错误中得到的是,一个线程在调用hash_table_remove 时试图释放已经被free(str_array[i]) 释放的内存。然而,这是没有意义的,因为hash_table_remove 在free(str_array[i] 之前被调用。我不知道是什么给了我这些无效的读数。
提前谢谢你!
【问题讨论】:
-
你确定你从文件中读取的数据是 '\0' 终止的吗?这可能会损坏您的字符串缓冲区。
-
strcpy处理\0终止,如果我没记错的话。谢谢你的回复! -
如果没有 MCVE (Minimal, Complete, Verifiable Example?)(或 MRE 或 SO 现在使用的任何名称;MCVE 已经使用了五年多,而且不需要更改),这将是地狱般的解决方案或 SSCCE (Short, Self-Contained, Correct Example) 让我们进行实验。非常糟糕,我不会费心去尝试它,直到有一个 minimal 示例(最小)数据可以重现问题。
-
如果源未终止,@saucygote strcpy 无法插入 \0。它将继续复制,直到到达可能超过字符串缓冲区末尾的 \0。但是你也会遇到 strlen 的同样问题。
-
@jo-art: 如果
fgets()成功,它总是返回一个字符串(这意味着它是空终止的)——源文件不需要有一个。但是,问题中的代码不会检查fgets()是否成功...
标签: c memory pthreads valgrind