MIT6.828_HW6_Threads and Locking
在此作业中,您将使用哈希表探索使用线程和锁的并行编程。 你应该在具有多个核心的真实计算机(不是xv6,而不是qemu)上完成这项功课。
开的虚拟机只有单CPU单核,所以两个线程与一个线程的程序跑出来是两倍的效果。需要先关闭虚拟机,修改处理器数1,核心数2。再次运行。
./a.out 2
0: put time = 0.007024
1: put time = 0.008762
0: get time = 6.424977
0: 2093 keys missing
1: get time = 6.535123
1: 2093 keys missing
completion time = 6.550038
./a.out 1
0: put time = 0.013344
0: get time = 6.519561
0: 0 keys missing
completion time = 6.533387
两点:1)1个线程完成时间与2个线程的完成时间大致相同,但2个线程操作数量是其两倍; 我们正在实现良好的并行性。 2)2个线程的输出表示 miss 了很多 keys 。 在你的运行中,可能会 miss 更多或更少的keys。如果您使用1个线程运行,则永远不会miss任何keys。
在put
与get
中加入互斥。
static
void put(int key, int value)
{
int i = key % NBUCKET;
pthread_mutex_lock(&lock);
insert(key, value, &table[i], table[i]);
pthread_mutex_unlock(&lock);
}
static struct entry*
get(int key)
{
struct entry *e = 0;
pthread_mutex_lock(&lock);
for (e = table[key % NBUCKET]; e != 0; e = e->next) {
if (e->key == key) break;
}
pthread_mutex_unlock(&lock);
return e;
}
首先阅读一下源码,弄明白为什么会产生missing。
- keys[]由随机数产生。
- 对应的线程在
put
时会将hash table中key值赋值为数组key[]某一段(以2个线程为例,就是0线程赋值为keys[]的前一半),value值置为其线程序号(0,1…等等)。 - 然后将各个key与value加入对应的桶中(有点像hash的链式解决冲突)。
而在多线程的情况下,当一个线程1进入insert()
还未进行插入时,轮转到另一个线程2进行了插入。回到线程1,此时的n还是为空,再进行连接时,就会丢失掉线程2插入的表项。
在put
与get
中加入互斥后,再次执行./a.out 2
, 确实不会有miss keys的问题了,但是完成时间却上升到15S之久。可见在操作临界区时,多线程带来的效果并不一定会更好,有时反而会更差。
./a.out 2
0: put time = 0.003763
1: put time = 0.014141
0: get time = 15.346588
0: 0 keys missing
1: get time = 15.346687
1: 0 keys missing
completion time = 15.365085
- 修改代码,以便在保持正确性的同时并行运行。 (提示:在这个应用程序中,“get”中为确保正确的锁是否必要?)
基于我们以上的分析,没有必要加入锁,因为其并不修改table。在去除get
中的锁后再次执行两个线程程序。完成时间降低到6.0s,并且没有missing。
./a.out 2
0: put time = 0.015451
1: put time = 0.016926
0: get time = 5.991161
0: 0 keys missing
1: get time = 5.998054
1: 0 keys missing
completion time = 6.015187
- 修改代码,使一些
put
操作并行运行,同时保持正确性。
最终put与get代码如下。
static
void put(int key, int value)
{
int i = key % NBUCKET;
pthread_mutex_lock(&lock);
insert(key, value, &table[i], table[i]);
pthread_mutex_unlock(&lock);
}
static struct entry*
get(int key)
{
struct entry *e = 0;
for (e = table[key % NBUCKET]; e != 0; e = e->next) {
if (e->key == key) break;
}
return e;
}