实验任务:
Insert lock and unlock statements in put and get so that the number of keys missing is always 0.
在put和get函数中插入lock和unlock语句,以便丢失的密钥数始终为0。
实验内容:
未插入lock和unlock语句时
$ gcc -g -O2 ph.c -pthread
$ ./a.out 2
运行命令得到以下结果
每个线程分两个阶段运行。在第一阶段,每个线程将NKEYS/nthread键放入哈希表中。在第二阶段,每个线程从哈希表中获取NKEYS。
$ ./a.out 1
再运行命令
可以看出,单线程与双线程完成时间大概相同,但是双线程做了两次get keys操作有效实现了并行;双线程会发生keys missing的情况。
查看代码分析出put key时调用insert函数时,会将新的entry插入到链表的头部,即修改链表头部指针,导致错误。
在put和get中插入lock和unlock语句如下
pthread_mutex_t lock; // declare a lock // pthread_mutex_init(&lock, NULL); // initialize the lock // pthread_mutex_lock(&lock); // acquire lock // pthread_mutex_unlock(&lock); // release lock static void put(int key, int value) { pthread_mutex_lock(&lock); // acquire lock int i = key % NBUCKET; insert(key, value, &table[i], table[i]); pthread_mutex_unlock(&lock); // release lock } static struct entry* get(int key) { //pthread_mutex_lock(&lock); // acquire lock struct entry *e = 0; for (e = table[key % NBUCKET]; e != 0; e = e->next) { if (e->key == key) break; } //pthread_mutex_unlock(&lock); // release lock return e; } |
再次进行测试
可以看到双线程key不再丢失,代码正确,但是完成时间变长了。
再次查看get部分代码,发现get操作并未涉及到修改hash表,即
只有读取操作,所以可以不用进行锁保护,所以我们取消get部分的锁,再次运行:
static struct entry* get(int key) { // pthread_mutex_lock(&lock); // acquire lock struct entry *e = 0; for (e = table[key % NBUCKET]; e != 0; e = e->next) { if (e->key == key) break; } // pthread_mutex_unlock(&lock); // release lock return e; } |
可以看到双线程仍然可以正确运行,且完成时间缩短。
再尝试对put中每一个bucket上锁,仍能正确运行。
pthread_mutex_t lock[NBUCKET]; static void put(int key, int value) { int i = key % NBUCKET; pthread_mutex_lock(&lock[i]); insert(key, value, &table[i], table[i]); pthread_mutex_unlock(&lock[i]); } |
此时每个bucket都有一个互斥锁,当执行put操作时,首先获取待处理bucket的锁,然后在完成后释放锁。 这允许同时访问不同的bucket而不会相互冲突,可以使put操作在保持正确性的同时并行运行。