(done) MIT6.S081 2023 学习笔记 (Day7: LAB6 Multithreading)
网页:https://pdos.csail.mit.edu/6.S081/2023/labs/thread.html
(任务1教会了你如何用 C 语言调用汇编,编译后链接即可)
任务1:Uthread: switching between threads (完成)
在这个练习中,你将设计一个用户级线程系统中的上下文切换机制,并实现它。为了帮助你开始,你的xv6系统中有两个文件 user/uthread.c 和 user/uthread_switch.S,以及Makefile中的一个规则来构建uthread程序。uthread.c 包含了大部分用户级线程包的代码和三个简单测试线程的代码。线程包缺少一些创建线程和在线程之间切换的代码。
根据讲义要求,当执行 uthread 程序时,应该出现如下输出:
我们先来运行试试,如下:
可以看到没有任何输出。先来看看 uthread 的源码:
int
main(int argc, char *argv[])
{a_started = b_started = c_started = 0;a_n = b_n = c_n = 0;thread_init();thread_create(thread_a);thread_create(thread_b);thread_create(thread_c);current_thread->state = FREE;thread_schedule();exit(0);
}
从 main 函数来看,可以看出大致逻辑和框架:初始化thread,创建三个线程,设置 main thread 状态为 FREE,让出执行流(类似于 yield 函数)。
先来看 thread_schedule() 源码:
void
thread_schedule(void)
{struct thread *t, *next_thread;/* Find another runnable thread. */next_thread = 0;t = current_thread + 1;for(int i = 0; i < MAX_THREAD; i++){if(t >= all_thread + MAX_THREAD)t = all_thread;if(t->state == RUNNABLE) {next_thread = t;break;}t = t + 1;}if (next_thread == 0) {printf("thread_schedule: no runnable threads\n");exit(-1);}if (current_thread != next_thread) { /* switch threads? */next_thread->state = RUNNING;t = current_thread;current_thread = next_thread;/* YOUR CODE HERE* Invoke thread_switch to switch from t to next_thread:* thread_switch(??, ??);*/} elsenext_thread = 0;
}
从函数源码来理解,是先从 thread 数组获取一个元素,随后调用 thread_switch
函数把执行流切换过去。这个 thread_switch
是需要我们自己实现的。
再看看 thread a b c 的源码:
void
thread_a(void)
{int i;printf("thread_a started\n");a_started = 1;while(b_started == 0 || c_started == 0)thread_yield();for (i = 0; i < 100; i++) {printf("thread_a %d\n", i);a_n += 1;thread_yield();}printf("thread_a: exit after %d\n", a_n);current_thread->state = FREE;thread_schedule();
}
其实就是不断打印东西,再切换到别的线程上。
思路其实很简单,跟着 xv6 的讲义做吧:
1.切换线程的函数 thread_switch 在 user/uthread_switch.S 中实现 (doing)
2.你的任务是制定一个计划来创建线程以及保存/恢复寄存器以在线程之间进行切换,并实现该计划。完成之后,运行 make grade 应该显示你的解决方案通过了uthread测试。
3.你需要在 user/uthread.c 中的 thread_create() 和 thread_schedule() 函数以及 user/uthread_switch.S 中的 thread_switch 添加代码。一个目标是确保当 thread_schedule() 第一次运行给定的线程时,线程能够在其自己的栈上执行传递给 thread_create() 的函数。另一个目标是确保 thread_switch 保存被切换离开的线程的寄存器,恢复被切换到的线程的寄存器,并返回到后者的线程指令中上次离开的位置。你需要决定在哪里保存/恢复寄存器;修改 struct thread 以保存寄存器是一个不错的计划。你需要在 thread_schedule 中添加对 thread_switch 的调用;你可以传递任何你需要给 thread_switch 的参数,但目的是从线程 t 切换到 next_thread。
4.thread_switch 只需要保存/恢复 callee-saved 的寄存器。为什么? (回答:编译器在编译 C 语言的时候,在编译对 thread_switch() 的函数调用时会自动保存 caller-saved 寄存器)
5.你可以在 user/uthread.asm 中查看 uthread 的汇编代码,这对于调试可能很有帮助。
6.为了测试你的代码,使用 riscv64-linux-gnu-gdb 单步执行 thread_switch 可能会有所帮助。你可以按照以下方式开始:
这块地方其实跟内核线程中的上下文切换很相似,我们定义一个如下的 context 结构体就可以把事情变得简单化:
struct context {uint64 ra;uint64 sp;// callee-saveduint64 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 context context;
};
后续很简单,自己悟吧。
运行 make grade,可以看到 任务1-uthread 已通过
任务2:Using threads (完成)
在这个作业中,你将使用哈希表探索基于线程和锁的并行编程。你应该在一个拥有多个核心的真实 Linux 或 MacOS 计算机上完成这个作业(不是 xv6,也不是 qemu)。大多数最新的笔记本电脑都配备了多核处理器。
这个作业使用了 UNIX pthread 线程库。你可以在手册页中找到关于它的信息,使用 man pthreads 命令,你也可以在网上查找,例如这里、这里和这里。
文件 notxv6/ph.c 包含一个简单的哈希表,如果从单个线程中使用它是正确的,但当从多个线程中使用时它是错误的。若运行:
make ph
./ph 1
会得到类似以下的输出:
100000 puts, 3.991 seconds, 25056 puts/second
0: 0 keys missing
100000 gets, 3.981 seconds, 25118 gets/second
你看到的数字可能与这个示例输出相差两倍或更多,这取决于你的计算机速度、是否有多个核心,以及它是否忙于执行其他任务。
ph 运行两个基准测试。首先,它通过调用 put() 向哈希表中添加大量键,并打印每秒 put 操作的速率。然后,它通过 get() 从哈希表中获取键。它打印出由于 put 操作应该存在于哈希表中的键的数量,但在这种情况下缺失的键数量(这里是零),以及它实现的每秒 get 操作的数量。
未经修改时运行 ph 2,会得到如下输出
这段 ph 2 输出的第一行表明,当两个线程同时向哈希表添加条目时,它们实现了每秒 53,044 次插入的总速率。这大约是运行 ph 1 单个线程速率的两倍。这是一个非常好的“并行加速”,大约是 2 倍,这是人们可能期望的最高加速(即核心数量加倍,每单位时间的工作量也加倍)。
然而,说有 16579 个键缺失的两行表明,大量应该存在于哈希表中的键却不在那里。也就是说,put 操作本应将这些键添加到哈希表中,但出了问题。请查看 notxv6/ph.c 文件,特别是 put() 和 insert() 函数。
问题:为什么在两个线程的情况下会有缺失的键,而在一个线程的情况下却没有?请识别出两个线程中的一系列事件,这些事件可能导致一个键的缺失。将你的事件序列和简短的解释提交到 answers-thread.txt 文件中。(回答看下面)
为了避免这一系列事件,请在 notxv6/ph.c 中的 put 和 get 函数中插入锁的获取和释放语句,以便在两个线程的情况下缺失的键数量始终为 0。相关的 pthread 调用如下:
pthread_mutex_t lock; // 声明一个锁
pthread_mutex_init(&lock, NULL); // 初始化锁
pthread_mutex_lock(&lock); // 获取锁
pthread_mutex_unlock(&lock); // 释放锁
当你完成这些修改,并且 make grade 显示你的代码通过了 ph_safe 测试(该测试要求两个线程下缺失的键数量为零)时,你就完成了这个任务。在这个阶段,ph_fast 测试失败是可以接受的。
不要忘记调用 pthread_mutex_init()。首先使用 1 个线程测试你的代码,然后用 2 个线程测试它。代码是否正确(即你是否消除了缺失的键?)两个线程的版本相对于单线程版本是否实现了并行加速(即每单位时间完成的总工作是否更多?)
存在这样的情况,即并发的 put() 操作在哈希表中的读写内存没有重叠,因此它们不需要锁来相互保护。你能修改 ph.c 来利用这种情况,以获得某些 put() 操作的并行加速吗?提示:每个哈希桶一个锁怎么样?
修改你的代码,以便一些 put 操作可以并行运行,同时保持正确性。当你完成修改并且 make grade 显示你的代码同时通过了 ph_safe 和 ph_fast 测试时,你就完成了任务。ph_fast 测试要求两个线程的 put 操作每秒至少是单个线程的 1.25 倍。
先来稍微读读 ph.c 源码,看为什么会 miss keys,仔细看对哈希表进行插入的代码:
static void
insert(int key, int value, struct entry **p, struct entry *n)
{struct entry *e = malloc(sizeof(struct entry));e->key = key;e->value = value;e->next = n; // 注意看这里 <--- 当两个线程同时执行到这里时,两个 key 的 next 都等于链表头*p = e; // 随后让链表头等于这两个 key,此时就会有一个 key 被遗漏掉
}
上面代码的注释就是对之前问题的回答
为了验证猜想,对 insert 函数调用加锁,如下:
// the new is new.pthread_mutex_lock(&lock); // 获取锁 insert(key, value, &table[i], table[i]);pthread_mutex_unlock(&lock); // 释放锁
编译运行,发现已经符合 ph_safe 了,如下
100000 puts, 2.288 seconds, 43702 puts/second
0: 0 keys missing
1: 0 keys missing
200000 gets, 4.546 seconds, 43995 gets/second
运行 make grade,可以同时通过 ph_safe 和 ph_fast,我们就不管了
任务3:Barrier (完成)
在这个作业中,你将实现一个 barrier:在应用程序中的一个点,所有参与的线程必须等待,直到所有其他参与的线程也到达那个点。你将使用 pthread 条件变量,这是一种类似于 xv6 的睡眠和唤醒的序列协调技术。
You should do this assignment on a real computer (not xv6, not qemu).
The file notxv6/barrier.c contains a broken barrier.
$ make barrier
$ ./barrier 2
barrier: notxv6/barrier.c:42: thread: Assertion `i == t' failed.
数字 2 指定了在屏障上同步的线程数量(在 barrier.c 中的 nthread)。每个线程执行一个循环。在每次循环迭代中,一个线程调用 barrier(),然后随机休眠一定数量的微秒。断言触发的原因是一个线程在另一个线程到达屏障之前离开了屏障。期望的行为是每个线程在 barrier() 中阻塞,直到所有 nthreads 个线程都调用了 barrier()。
你的目标是实现期望的屏障行为。除了你在 ph 作业中看到的锁原语之外,你还需要以下新的 pthread 原语;详细信息请查看这里和这里。
pthread_cond_wait(&cond, &mutex); // go to sleep on cond, releasing lock mutex, acquiring upon wake up
pthread_cond_broadcast(&cond); // wake up every thread sleeping on cond
Make sure your solution passes make grade’s barrier test.
pthread_cond_wait releases the mutex when called, and re-acquires the mutex before returning.
We have given you barrier_init(). Your job is to implement barrier() so that the panic doesn’t occur.
We’ve defined struct barrier for you; its fields are for your use.
There are two issues that complicate your task:
1.You have to deal with a succession of barrier calls, each of which we’ll call a round. bstate.round records the current round. You should increment bstate.round each time all threads have reached the barrier.
2.You have to handle the case in which one thread races around the loop before the others have exited the barrier. In particular, you are re-using the bstate.nthread variable from one round to the next. Make sure that a thread that leaves the barrier and races around the loop doesn’t increase bstate.nthread while a previous round is still using it.
Test your code with one, two, and more than two threads.
我们先来看 barrier 的 main 源码:
int
main(int argc, char *argv[])
{pthread_t *tha;void *value;long i;double t1, t0;if (argc < 2) {fprintf(stderr, "%s: %s nthread\n", argv[0], argv[0]);exit(-1);}nthread = atoi(argv[1]);tha = malloc(sizeof(pthread_t) * nthread);srandom(0);barrier_init();for(i = 0; i < nthread; i++) {assert(pthread_create(&tha[i], NULL, thread, (void *) i) == 0);}for(i = 0; i < nthread; i++) {assert(pthread_join(tha[i], &value) == 0);}printf("OK; passed\n");
}
可以看到程序先根据命令行参数创建几个线程,随后让这些线程执行 thread() 命令
看 thread() 函数实现:
static void *
thread(void *xa)
{long n = (long) xa;long delay;int i;for (i = 0; i < 20000; i++) {int t = bstate.round;assert (i == t);barrier();usleep(random() % 100);}return 0;
}
可以看到,这里要求所有线程在执行 for 循环时,i == 每一轮的 bstate.round。
而整个代码并没有 bstate.round 的修改,这也是我们要在 barrier() 函数中实现的。
按照要求在 barrier.c: barrier() 函数中实现后,运行 make grade
,如下:
已经获得满分
相关文章:

(done) MIT6.S081 2023 学习笔记 (Day7: LAB6 Multithreading)
网页:https://pdos.csail.mit.edu/6.S081/2023/labs/thread.html (任务1教会了你如何用 C 语言调用汇编,编译后链接即可) 任务1:Uthread: switching between threads (完成) 在这个练习中,你将设计一个用户级线程系统中的上下文切…...

面试经典150题——栈
文章目录 1、有效的括号1.1 题目链接1.2 题目描述1.3 解题代码1.4 解题思路 2、2.1 题目链接2.2 题目描述2.3 解题代码2.4 解题思路 3、最小栈3.1 题目链接3.2 题目描述3.3 解题代码3.4 解题思路 4、逆波兰表达式求值4.1 题目链接4.2 题目描述4.3 解题代码4.4 解题思路 5、基本…...

openmv的端口被拆分为两个 导致电脑无法访问openmv文件系统解决办法 openmv USB功能改动 openmv驱动被更改如何修复
我之前误打误撞遇到一次,直接把openmv的全部端口删除卸载然后重新插上就会自动重新装上一个openmv端口修复成功,大家可以先试试不行再用下面的方法 全部卸载再重新插拔openmv 要解决OpenMV IDE中出现的两个端口问题,可以尝试以下步骤&#x…...

自制虚拟机(C/C++)(三、做成标准GUI Windows软件,扩展指令集,直接支持img软盘)
开源地址:VMwork 要使终端不弹出, #pragma comment(linker, "/subsystem:windows /ENTRY:mainCRTStartup") 还要实现jmp near 0x01类似的 本次的main.cpp #include <graphics.h> #include <conio.h> #include <windows.h> #includ…...

算法题(56):旋转链表
审题: 我们需要根据k的大小把链表向右移动对应次数,并返回移动后的链表的头结点指针 思路: 根据提示中的数据大小我们发现:k的值可以远大于节点数。 也就是说我们对链表的操作存在周期,如果k%len0,说明我们…...

解决PyG安装中torch-sparse安装失败问题:详细指南
1 问题描述 最近在学习GNN,需要使用PyTorch Geometric(PyG)库。在安装PyG的过程中,遇到了torch-sparse安装失败的问题,错误提示为: ERROR: Failed building wheel for torch-sparse本文将详细记录问题的解…...

如何创建折叠式Title
文章目录 1 概念介绍2 使用方法3 示例代码 我们在上一章回中介绍了SliverGrid组件相关的内容,本章回中将介绍SliverAppBar组件.闲话休提,让我们一起Talk Flutter吧。 1 概念介绍 我们在本章回中介绍的SliverAppBar和普通的AppBar类似,它们的…...

go-zero学习笔记(三)
利用goctl生成rpc服务 编写proto文件 // 声明 proto 使用的语法版本 syntax "proto3";// proto 包名 package demoRpc;// golang 包名(可选) option go_package "./demo";// 如需为 .proto 文件添加注释,请使用 C/C 样式的 // 和 /* ... */…...

Wildcard工具详解:从入门到精通
1. Wildcard基础知识 什么是Wildcard? Wildcard(通配符)是一种用于匹配文件名或字符串的特殊字符。它允许用户使用简单的符号来表示复杂的匹配规则,从而快速定位目标文件或数据。 常见的Wildcard符号 *:匹配任意数量…...

冰蝎v3.0 beta7来啦
我用了一台kali,一台centos,一台windows,做了一个文件上传和一个反弹shell实验,载荷是AES加密的,终于感受到了对加密流量的无可奈何~ kali(php8.1)centos(php7.1)window…...

React中使用箭头函数定义事件处理程序
React中使用箭头函数定义事件处理程序 为什么使用箭头函数?1. 传递动态参数2. 避免闭包问题3. 确保每个方块的事件处理程序是独立的4. 代码可读性和维护性 示例代码总结 在React开发中,处理事件是一个常见的任务。特别是当我们需要传递动态参数时&#x…...

记忆化搜索和动态规划 --最长回文子串为例
记忆化搜索 记忆化搜索是一种优化递归算法的方法,通过将已经计算过的子问题的结果存储起来(通常使用哈希表或数组),避免重复计算相同的子问题。 本质上是通过缓存中间结果来减少计算的重复性。 动态规划 动态规划是通过将问题分…...

Tree Compass( Codeforces Round 934 (Div. 2) )
Tree Compass( Codeforces Round 934 (Div. 2) ) You are given a tree with n n n vertices numbered 1 , 2 , … , n 1, 2, \ldots, n 1,2,…,n. Initially, all vertices are colored white. You can perform the following two-step operation: …...

【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】2.17 掩码数组:缺失值处理的优雅方案
2.17 掩码数组:缺失值处理的优雅方案 目录 #mermaid-svg-12vjJJbyudPnkYBO {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-12vjJJbyudPnkYBO .error-icon{fill:#552222;}#mermaid-svg-12vjJJbyudPnkYBO…...

PHP 常用函数2025.02
PHP implode() 函数 语法 implode(separator,array) 参数描述separator可选。规定数组元素之间放置的内容。默认是 ""(空字符串)。array必需。要组合为字符串的数组。 技术细节 返回值:返回一个由数组元素组合成的字符串。PHP 版…...

react中如何获取dom元素
实现代码 const inputRef useRef(null) inputRef.current.focus()...

【C++】继承(下)
大家好,我是苏貝,本篇博客带大家了解C的继承(下),如果你觉得我写的还不错的话,可以给我一个赞👍吗,感谢❤️ 目录 5.继承与友元6.继承与静态成员7.复杂的菱形继承及菱形虚拟继承8.继…...

C语言实现字符串排序:从代码到原理深度解析
在编程的世界里,字符串处理是一项基础且重要的技能。今天,我们通过分析一段C语言代码来深入了解如何对字符串进行排序。 一、代码呈现 #include <stdio.h> #include <string.h> int main() { char s[1001]; scanf("%s", s); int…...

Vue3的el-table-column下拉输入实时查询API数据选择的实现方法
由于本人对el-table-column有下拉输入选择的要求,根据网上搜索的资料及本人优化,推出我比较满意的方法,供各位读者参考使用。 效果图 el-table-column写法 <el-table-columnlabel"货品编号"align"center"prop"…...

【数据结构】_链表经典算法OJ:复杂链表的复制
目录 1. 题目链接及描述 2. 解题思路 3. 程序 1. 题目链接及描述 题目链接:138. 随机链表的复制 - 力扣(LeetCode) 题目描述: 给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,…...

Vue 图片引用方式详解:静态资源与动态路径访问
目录 前言1. 引用 public/ 目录2. assets/ 目录3. 远程服务器4. Vue Router 动态访问5. 总结6. 扩展(图片不显示) 前言 🤟 找工作,来万码优才:👉 #小程序://万码优才/r6rqmzDaXpYkJZF 在 Vue 开发中&#x…...

chatGPT写的网页版贪吃蛇小游戏
chatGPT写的网页版贪吃蛇小游戏 前言网页版贪吃蛇小游戏 前言 之前无聊,让ChatGPT写了一段基于html语言的贪吃蛇小游戏代码 网页版贪吃蛇小游戏 将以下内容复制到记事本,重命名为xxx.html即可打开浏览器游玩 这里是一个使用HTML、CSS和JavaScript编写…...

Python量化交易助手:xtquant的安装与应用
Python量化交易助手:xtquant的安装与应用 技术背景和应用场景 在量化交易领域,Python因其强大的库支持和灵活性成为了许多开发者的首选语言。其中,xtquant 是迅投官方开发的一个Python包,专门用于与miniqmt通信,实现…...

前缀和算法
文章目录 算法总览题目1371.每个元音包含偶数次的最长子字符串 算法总览 题目 1371.每个元音包含偶数次的最长子字符串 1371.每个元音包含偶数次的最长子字符串 参考博主的讲解 思路分析:就是得使用前缀和记录情况,dp[i][j]表示s[0] 到s[i] 中&…...

Qt常用控件 输入类控件
文章目录 1.QLineEdit1.1 常用属性1.2 常用信号1.3 例子1,录入用户信息1.4 例子2,正则验证手机号1.5 例子3,验证输入的密码1.6 例子4,显示密码 2. QTextEdit2.1 常用属性2.2 常用信号2.3 例子1,获取输入框的内容2.4 例…...

《最小阻力之路》关于愿景的理解和思考
一、愿景的形成机制 1. 愿景的三层来源 来源层级形成机制案例潜在偏差风险① 本能冲动层对快感/痛苦的即时反应"想暴富"源于缺钱焦虑易被短期情绪劫持② 社会镜像层内化外界标准(家庭/社会/文化)"必须考研"因家人期待混淆他人需求…...

Ubuntu 22.04系统安装部署Kubernetes v1.29.13集群
Ubuntu 22.04系统安装部署Kubernetes v1.29.13集群 简介Kubernetes 的工作流程概述Kubernetes v1.29.13 版本Ubuntu 22.04 系统安装部署 Kubernetes v1.29.13 集群 1 环境准备1.1 集群IP规划1.2 初始化步骤(各个节点都需执行)1.2.1 主机名与IP地址解析1.…...

虚幻基础17:动画层接口
能帮到你的话,就给个赞吧 😘 文章目录 animation layer interface animation layer interface 动画层接口:动画图表的集。仅有名字。 添加到动画蓝图中,由动画蓝图实现动画图表。...

无人机PX4飞控 | PX4源码添加自定义uORB消息并保存到日志
PX4源码添加自定义uORB消息并保存到日志 0 前言 PX4的内部通信机制主要依赖于uORB(Micro Object Request Broker),这是一种跨进程的通信机制,一种轻量级的中间件,用于在PX4飞控系统的各个模块之间进行高效的数据交换…...

HTMLCSS :下雪了
这段代码创建了一个动态的雪花飘落加载动画,通过 CSS 技术实现了雪花的下落和消失效果,为页面添加了视觉吸引力和动态感。 大家复制代码时,可能会因格式转换出现错乱,导致样式失效。建议先少量复制代码进行测试,若未能…...