MIT6.s081_Lab7 thread: Multithreading

MIT6.s081 Lab7:Multithreading

总体来说,是最简单的一个,基本不涉及到内核,但是需要理解函数,以及线程的切换。

代码

1. Uthread: switching between threads

这个题目需要理解题目的意思,以及线程切换的基本知识,我在这里花了挺长时间的,当时做的时候没带脑子,因为不同的线程需要使用不同的栈没考虑,导致输出一直是错的,debug了很久。

  1. 增加线程切换必须的上下文环境,必须要保存的14个寄存器。

    struct thread {
      char       stack[STACK_SIZE]; /* the thread's stack */
      int        state;             /* FREE, RUNNING, RUNNABLE */
    
      uint64     ctx[15];
    };
    

    因为在uthread中每个线程的运行环境是不一样的,切换线程需要保存栈无法保存的数据。

  2. 线程切换 。

    void 
    thread_schedule(void)
    {
    ……
      if (current_thread != next_thread) {         /* switch threads?  */
        next_thread->state = RUNNING;
        t = current_thread;
        current_thread = next_thread;
        thread_switch((uint64)t->ctx, (uint64)current_thread->ctx);
      } else
        next_thread = 0;
    }
    

    线程调度的时候,需要返回另一个线程的ra,以及恢复线程的上下文环境,其实就是做一个上一个线程和即将运行的线程的一个上下文切换。

  3. 初始化线程

    void 
    thread_create(void (*func)())
    {
      struct thread *t;
    
      for (t = all_thread; t < all_thread + MAX_THREAD; t++) {
        if (t->state == FREE) break;
      }
      t->state = RUNNABLE;
      uint64 *func_addr = t->ctx;
      *func_addr = (uint64)(func);
      t->ctx[14] = (uint64)t->stack + STACK_SIZE;
    }
    

    这边对线程进行初始化,因为线程调度器需要返回上次运行的地方,也就是ra,因为这里是初始化,因此只需要把ra设置为函数的地址即可。当时没想到的是sp,也就是线程栈,每个线程运行是需要不同的栈来保存运行的必要信息的,也不知到当时为什么没想到,以为保存tp就可以解决问题,在这debug很久……sp需要在最高的地址,因为xv6中是递减的,所以加了`STACK_SIZE。

  4. 完善thread_switch

    thread_switch:
    	sd		ra, 0(a0)
    	sd 		tp, 8(a0)
    	sd  	s0, 16(a0)
    	sd  	s1, 24(a0)
    	sd  	s2, 32(a0)
    	sd  	s3, 40(a0)
    	sd  	s4, 48(a0)
    	sd  	s5, 56(a0)
    	sd  	s6, 64(a0)
    	sd  	s7, 72(a0)
    	sd  	s8, 80(a0)
    	sd  	s9, 88(a0)
    	sd  	s10, 96(a0)
    	sd  	s11, 104(a0)
    	sd 		sp, 112(a0)
    
    	ld		ra, 0(a1)
    	ld 		tp, 8(a1)
    	ld  	s0, 16(a1)
    	ld  	s1, 24(a1)
    	ld  	s2, 32(a1)
    	ld  	s3, 40(a1)
    	ld  	s4, 48(a1)
    	ld  	s5, 56(a1)
    	ld  	s6, 64(a1)
    	ld  	s7, 72(a1)
    	ld  	s8, 80(a1)
    	ld  	s9, 88(a1)
    	ld  	s10, 96(a1)
    	ld  	s11, 104(a1)
    	ld 		sp, 112(a1)
    	ret    /* return to ra */
    

2. Using threads

其实从测试来看,只测试了缺失的键,因此只要在插入新的键时加入锁,防止重复进行头插。

这是一个场景。

NBUCKET是5,假设现在又线程A和线程B
线程A put (13, 4)
线程B put (8, 6)
此时两个线程都会在table[3]插入数据
两个线程同时执行到这里,insert(key, value, &table[i], table[i]);
这个时候会得到一样的&table[i],这个时候插入(头插法),数据会丢失

还是比较容易的,做出来的时候以为做错了。

pthread_mutex_t lock; 
pthread_mutex_init(&lock, NULL);

    pthread_mutex_lock(&lock); 
    insert(key, value, &table[i], table[i]);
    pthread_mutex_unlock(&lock);

3. Barrier

目标就是,所有的线程必须同时在一个i上,不能有先跑的。大概思路就是在barrier中,先判断是否所有线程都执行过一次了,是的话就进行下一个i,也就是更新round,不是就睡觉。需要注意的是,在执行操作时,不能有两个线程同时进行,否则可能会导致数据错误,比如更新nthtread的时候,两个线程同时执行了加操作,但是最后结果只加1,属于race,要加锁,其次就是pthread_cond_wait,会释放锁,被唤醒的时候还会加锁,这里还调试了一下,没仔细看函数功能的恶果。

static void 
barrier()
{
  pthread_mutex_lock(&bstate.barrier_mutex);
  bstate.nthread++;
  if(bstate.nthread == nthread) {
    bstate.nthread = 0;
    bstate.round++;
    pthread_cond_broadcast(&bstate.barrier_cond);
  } else {
    pthread_cond_wait(&bstate.barrier_cond, &bstate.barrier_mutex);
  }
  pthread_mutex_unlock(&bstate.barrier_mutex);
}
posted @ 2025-07-25 01:28  BuerH  阅读(8)  评论(0)    收藏  举报