MIT6.s081_Lab7 thread: Multithreading
MIT6.s081 Lab7:Multithreading
总体来说,是最简单的一个,基本不涉及到内核,但是需要理解函数,以及线程的切换。
1. Uthread: switching between threads
这个题目需要理解题目的意思,以及线程切换的基本知识,我在这里花了挺长时间的,当时做的时候没带脑子,因为不同的线程需要使用不同的栈没考虑,导致输出一直是错的,debug了很久。
-
增加线程切换必须的上下文环境,必须要保存的14个寄存器。
struct thread { char stack[STACK_SIZE]; /* the thread's stack */ int state; /* FREE, RUNNING, RUNNABLE */ uint64 ctx[15]; };因为在
uthread中每个线程的运行环境是不一样的,切换线程需要保存栈无法保存的数据。 -
线程切换 。
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,以及恢复线程的上下文环境,其实就是做一个上一个线程和即将运行的线程的一个上下文切换。
-
初始化线程
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。 -
完善
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);
}

浙公网安备 33010602011771号