在这个作业中,我们将使用一个Hash Table实现多线程编程,首先下载这个文件。
作业内容
下载文件后进行编译并运行:
1 | $ gcc -g -O2 ph.c -pthread |
2表示启动的线程数,运行一段时间后,程序应该有如下输出
1 | 1: put time = 0.003338 |
每个线程运行在两个阶段,put和get,每个阶段大约7.7秒,现在使用一个线程:
1 | $ ./a.out 1 |
可以发现总用时为6.9秒,貌似一个线程速度还快一些,但是多线程做了其两倍的工作,用时只多了不到1秒,还是效率高了一些,但是可以看到,使用两个线程会有很多次哈希miss的情况,画一个图思考一下为何会出现哈希miss。(本质上是因为两个线程对同一个空间进行写操作,第一个线程写了之后,被第二个线程擦除,所以真正有用的是第二个线程的操作)
为了避免哈希miss,我们需要在对hash table访问时加锁保护,使用下列语句对put和get进行加锁:
1 | pthread_mutex_t lock; // declare a lock |
由于get只是读取,因此我们只需要对put进行加锁,即可避免hash miss
反思
粗粒锁与细粒锁
我们可以使用一个锁锁住整个哈希表,也可以对哈希表中的每一个链表单独上一把锁,代码分别如下:
粗颗粒锁
粗颗粒锁对整个哈希表上锁,比较简单,但是控制颗粒度很粗,访问不同列的线程也会导致相互阻塞。
1 | pthread_mutex_t lock; // declare a lock |
细颗粒锁
细颗粒锁针对哈希表每一列单独上锁,因此访问不同列的线程不会因为争抢锁而陷入等待,效率高一些。
1 | pthread_mutex_t locks[NBUCKET]; // declare a lock |
两种不同锁对比
总的来说粗颗粒锁更简单,细颗粒锁更快一些,但是需要锁的数量会上升,如果对象很多的话,使用细颗粒锁并不方便。(其实就我个人的实验,并没有看出两者之间有何差别)
源码
1 | // Headers |