本节中,我们将使用哈希表来探索线程和锁的并行编程,这个任务应该在一个具有多核的真实的计算机上运行(而非xv6或QEMU)
Step1:下载https://pdos.csail.mit.edu/6.828/2018/homework/ph.c(ph.c)并在计算机上使用如下指令进行编译:
$ gcc -g -O2 ph.c -pthread
$ ./a.out 2
参数2指出会有2个线程对哈希表进行get和put操作,程序运行一段时间后,会打印出如下信息
1: put time = 0.003338
0: put time = 0.003389
0: get time = 7.684335
0: 17480 keys missing
1: get time = 7.684335
1: 17480 keys missing
completion time = 7.687856
每个线程都会做两个阶段的任务,第一阶段将NKEYS/nthreads个键值插入哈希表中,第二阶段从哈希表中获取NKEYS个键值,上述打印出来的信息给出了每个线程执行每个阶段所耗费的时间。和单线程执行该任务的情况比较一下:
$ ./a.out 1
0: put time = 0.004073
0: get time = 6.929189
0: 0 keys missing
completion time = 6.933433
单线程耗费时间比双线程要少一点,但实际上双线程在执行第二阶段get任务时,做了两倍于单线程的工作,所以双线程实际上实现了双倍的速度优化(在多核处理器上)。
运行此应用程序时,如果是在单核计算机上运行,或者该计算机正忙于运行其他应用程序,则可能看不到并行性。
两点:1)1个线程完成时间与2个线程的完成时间大致相同,但2个线程在get阶段做的工作是其两倍; 展现了良好的并行性。 2)2个线程的输出说明丢失了很多 keys ,在多次测试运行结果中,可能会 丢失更多或更少的keys。但若使用1个线程运行,则永远不会丢失任何keys。
问题:为什么双线程执行任务会丢失keys而单线程不会呢?
答:哈希表是临界资源,不加锁直接进行访问可能导致数据被覆盖,具体实例见下图。
Step2:为了避免发生丢失key的情况,请在put和get中插入lock和unlock语句,来使丢失的键数始终为0。相关的pthread调用为
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
修改后的put和get代码:
首先使用单线程测试代码,然后使用双线程测试它, 看看是否解决了丢失键值的问题,双线程版本是否比单线程版本更快?
双线程测试与单线程测试对比:
答:从上图可以看到加入互斥锁后,确实解决了丢失keys的问题,但同时也牺牲了双线程执行的并行性,在加入锁后,双线程同一时间实际上只能有一个线程对哈希表进行操作,可以看做是串行执行put和get任务
,另外,由于get所有keys的任务做了两次,所以双线程执行的时间要比单线程执行的时间长的多。
Step3:修改代码,在保证不丢失keys的情况下,使get keys的任务能够并行执行(提示:有必要在get函数里面加锁吗?)
get函数只是对临界资源做简单的查找工作,不可能发生覆盖keys的情况,也不会导致丢失keys,所以get函数里面没有必要加锁,去除加锁的代码后,get keys的任务能够并行执行了,完成任务的时间也缩短了(实际上如果get阶段任务的完成速度要比put阶段任务完成速度快的多的话,那么也可能会发生丢失keys的情况,因为可能发生这种情况:线程2的put任务还没完成,线程1在get keys过程中查找不到线程2应该put的那部分keys)。
Step4:修改代码,来使某些put操作可以并行运行,同时保证不丢失keys。 (提示:每个bucket都要上锁吗?
实际上,双线程的put操作在i = key%NBUCKET不同时,是不会导致覆盖问题的,因为两个线程插入的不是同一个链表,也就是说i不同时,put操作是可以并行执行的,那我们可以声明一个mutex数组,来保证i相同时,才对同一链表进行互斥访问。
修改代码如下:
声明:
pthread_mutex_t lock[NBUCKET];
put函数:
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]);
}
main函数:
for(int i=0;ipthread_mutex_init(&lock[i],NULL);