• [mit6.s081] 笔记 Lab7: Multithreading


    前言

    本实验将使您熟悉多线程。您将在用户级线程包中实现线程之间的切换,使用多个线程来加速程序,并实现一个屏障。

    Attention
    在编写代码之前,您应该确保已经阅读了xv6手册中的“第7章: 调度”,并研究了相应的代码。

    Uthread: switching between threads (moderate)

    在本练习中,您将为用户级线程系统设计上下文切换机制,然后实现它。为了让您开始,您的xv6有两个文件:user/uthread.c和user/uthread_switch.S,以及一个规则:运行在Makefile中以构建uthread程序。uthread.c包含大多数用户级线程包,以及三个简单测试线程的代码。线程包缺少一些用于创建线程和在线程之间切换的代码。

    YOU JOB
    您的工作是提出一个创建线程和保存/恢复寄存器以在线程之间切换的计划,并实现该计划。完成后,make grade应该表明您的解决方案通过了uthread测试。

    这一部分的实现可以参考 kernel/swtch.S、kernel/proc.h

    struct thread

    因为在切换线程的过程中需要保存寄存器,所已需要在thread里增添一个用来保存寄存器的变量,

    struct thread_frame
    {
      uint64 ra;
      uint64 sp;
      // callee-saved
      uint64 s0;
      uint64 s1;
      uint64 s2;
      uint64 s3;
      uint64 s4;
      uint64 s5;
      uint64 s6;
      uint64 s7;
      uint64 s8;
      uint64 s9;
      uint64 s10;
      uint64 s11;
    };
    struct thread
    {
      char stack[STACK_SIZE];            /* the thread's stack */
      int state;                         /* FREE, RUNNING, RUNNABLE */
      struct thread_frame thread_context; //用来保存寄存器
    };
    

    thread_create

    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;
      // YOUR CODE HERE
      t->thread_context.ra=(uint64)func;
      t->thread_context.sp=(uint64)t->stack+STACK_SIZE;
    }
    

    修改 ra、sp

    thread_schedule

    void thread_schedule(void)
    {
      struct thread *t, *next_thread;
    
      /* Find another runnable thread. */
          //省略 ``````````````````
      for (int i = 0; i < MAX_THREAD; i++)
      {
              //省略 ``````````````````
        thread_switch((uint64)&t->thread_context,(uint64)&current_thread->thread_context);
      }
      else
        next_thread = 0;
    }
    

    测试

    $ make qemu
    ...
    $ uthread
    thread_a started
    thread_b started
    thread_c started
    thread_c 0
    thread_a 0
    thread_b 0
    thread_c 1
    thread_a 1
    thread_b 1
    ...
    thread_c 99
    thread_a 99
    thread_b 99
    thread_c: exit after 100
    thread_a: exit after 100
    thread_b: exit after 100
    thread_schedule: no runnable threads
    

    Using threads (moderate)

    • 使用 pthreads 库进行多线程编程
    • 在真正的 Linux 机器上跑代码

    YOUR JOB
    为什么两个线程都丢失了键,而不是一个线程?确定可能导致键丢失的具有2个线程的事件序列。在answers-thread.txt中提交您的序列和简短解释。
    [!TIP] 为了避免这种事件序列,请在notxv6/ph.c中的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
    • 当make grade说您的代码通过ph_safe测试时,您就完成了,该测试需要两个线程的键缺
      失数为0。在此时,ph_fast测试失败是正常的。

    分析

    因为多个线程同时访问同一块内存是不安全的,所以需要进行加锁

    我们可以为每一个散列桶加一个锁,防止多线程同时写同一个散列桶的内容

    main

    int main(int argc, char *argv[])
    {
    
      //`````````````省略
      //初始化互斥锁
      for(int i=0;i<NBUCKET;i++)
      {
        pthread_mutex_init(&locks[i],NULL);
      }
     //`````````````省略
      //销毁锁
      for(int i=0;i<NBUCKET;i++)
      {
        pthread_mutex_destroy(&locks[i]);
      }
     //`````````````省略
    }
    

    put

    在写前加锁,写后解锁

    static void put(int key, int value)
    {
     //````````````省略
      pthread_mutex_lock(&locks[i]);   //加锁
      if (e)
      {
        // update the existing key.
        e->value = value;
      }
      else
      {
        // the new is new.
        insert(key, value, &table[i], table[i]);
      }
      pthread_mutex_unlock(&locks[i]); //释放锁
    }
    

    测试

    $ ./ph 2
    100000 puts, 2.751 seconds, 36349 puts/second
    1: 0 keys missing
    0: 0 keys missing
    200000 gets, 5.685 seconds, 35181 gets/second

    Barrier(moderate)

    要求

    • 实现一个 barrier(应用程序中的一个点),所有参与线程必须在该点等待,直到所有其他参与线程也都到达该点
    • Linux 条件变量实现
    • 需要在真正的 Linux 机器上跑代码

    您的目标是实现期望的屏障行为。除了在ph作业中看到的lock原语外,还需要以下新的pthread原语;详情请看这里和这里。

    • pthread_cond_wait(&cond, &mutex);
      // 在cond上进入睡眠,释放锁mutex,在醒来时重新获取
    • pthread_cond_broadcast(&cond);
      // 唤醒睡在cond的所有线程

    barrier

    static void 
    barrier()
    {
      pthread_mutex_lock(&bstate.barrier_mutex);
      bstate.nthread++;
      if(bstate.nthread!=nthread) //如果所有线程还没到达,就阻塞
      {
        pthread_cond_wait(&bstate.barrier_cond,&bstate.barrier_mutex);
      }
      else  //所有线程都到达了
      {
        bstate.round++;
        bstate.nthread=0;
        pthread_cond_broadcast(&bstate.barrier_cond);
      }
      pthread_mutex_unlock(&bstate.barrier_mutex);
      
    }
    

    测试

    ./barrier 2
    OK; passed

    实验结果

    $ make qemu-gdb
    uthread: OK (2.6s)
    == Test answers-thread.txt == answers-thread.txt: OK
    == Test ph_safe == make[1]: 进入目录“/home/gty/vscode/MIT6.S081/xv6-labs-2021”
    gcc -o ph -g -O2 -DSOL_THREAD -DLAB_THREAD notxv6/ph.c -pthread
    make[1]: 离开目录“/home/gty/vscode/MIT6.S081/xv6-labs-2021”
    ph_safe: OK (8.7s)
    == Test ph_fast == make[1]: 进入目录“/home/gty/vscode/MIT6.S081/xv6-labs-2021”
    make[1]: “ph”已是最新。
    make[1]: 离开目录“/home/gty/vscode/MIT6.S081/xv6-labs-2021”
    ph_fast: OK (20.5s)
    == Test barrier == make[1]: 进入目录“/home/gty/vscode/MIT6.S081/xv6-labs-2021”
    gcc -o barrier -g -O2 -DSOL_THREAD -DLAB_THREAD notxv6/barrier.c -pthread
    make[1]: 离开目录“/home/gty/vscode/MIT6.S081/xv6-labs-2021”
    barrier: OK (2.9s)
    == Test time ==
    time: OK
    Score: 60/60

  • 相关阅读:
    课时七—进程同步(一)
    GNN+RA 文献阅读
    艾泊宇产品战略:灵感于鬼屋,掌握打造卓越用户体验的关键要素
    React Context源码是怎么实现的呢
    stm32—GPIO
    C语言 —— 函数
    探花交友_第4章_MongoDB基础(新版)
    玻色量子签约移动云“五岳”量子云计算创新加速计划!
    Hadoop环境快速搭建(伪分布式和完全分布式步骤版)
    35.【C/C++ 枚举(bool)类型和宏定义 (超详细)】
  • 原文地址:https://blog.csdn.net/m0_61705102/article/details/126957163