MIT6.828学习之homework6:Threads and Locking

新知识

1.哈希表
2.如果没有阻塞或者等待,主线程会直接运行下去,并不会等待子线程执行结束。这里是靠pthread_join使主线程等待两个子线程结束。也可以通过sleap
3.在用户没有设定线程间的调度策略时,系统默认采取基于时间片轮转的调度策略。所以当NKEYS=100000时,会出现两个线程的多次轮转,也才有可能导致key的丢失。当我把NKEYS改成10的时候,就算不用put里也不加锁,也并没有丢失任何key,说明了这一点。执行顺序参考了李亚超兄弟
4.多线程可能影响并行速度,所以可能并不一定多线程就是好的
5. locks有助于正确地共享数据; locks可以限制并行加速
6. parallelism(并行性)
7. 可以参考Lecture9

实验过程

在本作业中,您将使用a hash table(哈希表)研究threads and locks的并行编程。您应该在具有多个核心的真实计算机(不是xv6,也不是qemu)上完成这项作业。

在自己的操作系统上使用下面命令

$ gcc -g -O2 ph.c -pthread
$ ./a.out 2

2指定在哈希表上执行put和get操作的线程数。结果如下:
在这里插入图片描述
每个线程分两个阶段(phases)运行。在第一个阶段,每个线程将NKEYS/nthread keys放入the hash table。在第二阶段,每个线程从散列表中获取NKEYS。print语句告诉您每个阶段为每个线程花费了多长时间。底部的completion time告诉您应用程序的总运行时间。在上面的输出中,应用程序的完成时间约为30.5秒。每个线程计算大约30.5秒(put ~0.0 + get ~30.5)。

要查看使用两个线程是否提高了性能,让我们与单个线程进行比较$ ./a.out 1:
在这里插入图片描述
这就尴尬了,homework页面里的两个线程任务7.7秒,一个线程也要7.0秒,自然是提高了近一倍的速度。但是我这里两个线程30.5,一个线程只要14.0,那岂不是还更慢些了?尴尬。。。

好吧,原来是我的虚拟机设置的是单核的,改成双核的再试一次:
在这里插入图片描述

两个重点:1)完成时间与2个线程大致相同,但是双线程get的线程数是单线程的两倍,即双线程遍历了两次哈希表,我们正在实现良好的并行性。2)两个线程的输出表明丢失了许多keys。在您的运行中,可能会丢失或多或少的键。如果使用一个线程运行,就不会丢失任何键

我发现我的线程的执行顺序好像跟别人的不一样。而且不改代码,连续运行两遍,执行顺序也不一样?执行顺序到底是什么样的?
答:一开始,应该是在主线程里,然后主线程因为pthread_join而要等线程0和线程1结束。然后线程0与线程1都开始put,因为__sync_fetch_and_add需要等所有线程完成put操作,所以先完成put操作的子线程会print出put的时间然后阻塞等另一个线程完成put操作。然后再通过get来遍历哈希表。所以顺序不一样应该是正常的,看哪边先put完,跟机器应该也有关系

为什么有两个或多个线程丢失键,而一个线程时却不会?识别可能导致两个线程丢失键的事件序列
可能线程1在线程0执行完insert函数中的e->next = n;后执行了insert操作,此时新增的两个Entry都指向n,并且线程1的Entry作为链表头放入了bucket,然后线程0的Entry也会放入,这样就覆盖了线程1的那个,就是丢失了一个key。(尽管线程1的key1与线程0的key2不相等,但当key1%NBUCKET == key2%NBUCKET == i时,就有可能同时想插入bucket[i]中)

为了避免这个事件序列,在putget中插入lockunlock语句,使得丢失的键数总是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

修改如下:

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;
}

首先用一个线程测试代码,然后用两个线程测试它。它是正确的吗?(也就是说,你已经把丢失的keys清除了吗?)双线程版本比单线程版本快吗?
在这里插入图片描述
确实没有再miss keys了,但是双线程时间变成34.5秒,比单线程13秒的两倍还多。说明双线程并不比单线程版本快,所以有时候多线程并不总是好的。

修改代码,以便在保持正确性的同时并行运行get操作。(提示:在这个应用程序中,为了保证正确性,locksget中是必需的吗?)
答:get只是遍历哈希表,并不会改变哈希表,所以加不加锁都不会导致key丢失。所以单纯只讨论key会不会丢失时get里不用加locks

修改代码,使某些put操作并行运行,同时保持正确性。(提示:每个bucket(桶?)都有a lock吗?)
不懂什么意思。。。按照逻辑来看,为了防止两个线程同时往同一个bucket里insert才加的锁,所以应该确实时每个bucket都有a lock。

修改如下:

static void put(int key, int value)
{
  pthread_mutex_lock(&lock);  // acquire lock
  int i = key % NBUCKET; //i为bucket的索引值
  insert(key, value, &table[i], table[i]);
  pthread_mutex_unlock(&lock);  // release 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;
}

最后一次运行:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值