(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,如下:
在這里插入圖片描述

已經獲得滿分


本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/67884.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/67884.shtml
英文地址,請注明出處:http://en.pswp.cn/web/67884.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Kubernetes學習之通過Service訪問Pod

一、基礎概述 1.當通過deployment等controller動態創建和銷毀pod使得每個pod都有自己的ip地址&#xff0c;當controller用新的pod替代發生故障的pod時&#xff0c;新的pod會分配到新的ip地址&#xff0c;那么客戶端如何穩定的找到并訪問pod提供的服務。 2.創建service service從…

【優先算法】專題——前綴和

目錄 一、【模版】前綴和 參考代碼&#xff1a; 二、【模版】 二維前綴和 參考代碼&#xff1a; 三、尋找數組的中心下標 參考代碼&#xff1a; 四、除自身以外數組的乘積 參考代碼&#xff1a; 五、和為K的子數組 參考代碼&#xff1a; 六、和可被K整除的子數組 參…

CDDIS從2025年2月開始數據遷移

CDDIS 將從 2025 年 2 月開始將我們的網站從 cddis.nasa.gov 遷移到 earthdata.nasa.gov&#xff0c;并于 2025 年 6 月結束。 期間可能對GAMIT聯網數據下載造成影響。

谷歌Titans模型論文解析,Transformer迎來變革拐點——DeepSeek能否“接招”?

一、引入 Titans 模型 我們將深入探討谷歌研究院的一篇新論文《Titans: Learning to Memorize at Test Time》&#xff0c;該論文介紹了一種名為 Titans 的新模型架構。 Titans 在緩解 Transformer 二次方成本問題的同時&#xff0c;展現出了令人期待的成果。Titans 模型的設…

新春賀歲,共赴AGI之旅

點擊藍字 關注我們 AI TIME歡迎每一位AI愛好者的加入&#xff01; 往期精彩文章推薦 季姮教授獨家文字版干貨 | 面向知識淵博的大語言模型 關于AI TIME AI TIME源起于2019年&#xff0c;旨在發揚科學思辨精神&#xff0c;邀請各界人士對人工智能理論、算法和場景應用的本質問題…

Baklib推動數字化內容管理解決方案助力企業數字化轉型

內容概要 在當今信息爆炸的時代&#xff0c;數字化內容管理成為企業提升效率和競爭力的關鍵。企業在面對大量數據時&#xff0c;如何高效地存儲、分類與檢索信息&#xff0c;直接關系到其經營的成敗。數字化內容管理不僅限于簡單的文檔存儲&#xff0c;更是整合了文檔、圖像、…

【memgpt】letta 課程4:基于latta框架構建MemGpt代理并與之交互

Lab 3: Building Agents with memory 基于latta框架構建MemGpt代理并與之交互理解代理狀態,例如作為系統提示符、工具和agent的內存查看和編輯代理存檔內存MemGPT 代理是有狀態的 agents的設計思路 每個步驟都要定義代理行為 Letta agents persist information over time and…

測試方案和測試計劃相同點和不同點

在軟件測試領域&#xff0c;測試方案與測試計劃皆為舉足輕重的關鍵文檔&#xff0c;盡管它們有著緊密的關聯&#xff0c;但在目的與內容層面存在著顯著的差異。相同點&#xff1a; 1.共同目標&#xff1a;測試方案和測試計劃的核心目標高度一致&#xff0c;均致力于保障軟件的…

詳細介紹:網站背景更換功能

目錄 1. HTML 部分 2. JavaScript 部分 3. 完整流程 4. 總結 5. 適用場景 本文將介紹如何通過文件上傳實現網站背景圖片的更換。通過使用 JavaScript 和 Axios&#xff0c;我們可以允許用戶上傳圖片文件并將其作為網站的背景圖片。上傳的圖片 URL 會保存在瀏覽器的 localSt…

嵌入原則:數據特征如何 融入 模型的 損失地形

嵌入原則&#xff1a;數據特征如何 融入 模型的 損失地形 第一節&#xff1a;嵌入原則的基本概念與公式解釋 機器學習中的嵌入原則&#xff0c;就像 “雕刻師” 將 “石塊的紋理” 逐漸融入到 “雕塑的造型” 中。數據特征不再是獨立的輸入&#xff0c;而是被模型 “吸收” 和…

FPGA|例化生成的PLL功能IP核

1、例化上一篇文章中調用的IP核&#xff0c;新建文件PLL_test.v 2、代碼如圖 timescale 1ns / 1ps module PLL_test(input clk,input rst_n,output clkout0,output clkout1,output clkout2,output clkout3,output clkout4);wire locked;PLL pll_inst(.inclk0(clk),.c0(clkout0)…

【C++】P5734 【深基6.例6】文字處理軟件

博客主頁&#xff1a; [小????????] 本文專欄: C 文章目錄 &#x1f4af;前言&#x1f4af;題目描述&#x1f4af;題目描述輸入格式輸出格式示例輸入與輸出輸入&#xff1a;輸出&#xff1a; &#x1f4af;我的做法操作1&#xff1a;在文檔末尾插入字符串操作2&…

后盾人JS -- 原型

沒有原型的對象 也有沒有原型的對象 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document<…

洛谷 P1130 紅牌 C語言

題目描述 某地臨時居民想獲得長期居住權就必須申請拿到紅牌。獲得紅牌的過程是相當復雜&#xff0c;一共包括 N 個步驟。每一步驟都由政府的某個工作人員負責檢查你所提交的材料是否符合條件。為了加快進程&#xff0c;每一步政府都派了 M 個工作人員來檢查材料。不幸的是&…

【線程】基于環形隊列的生產者消費者模型

1 環形隊列 環形隊列采用數組來模擬&#xff0c;用取模運算來模擬環狀特性。 1.如何判斷環形隊列為空或者為滿? 當環形隊列為空時&#xff0c;頭和尾都指向同一個位置。當環形隊列為滿時&#xff0c;頭和尾也都指向同一個位置。 因此&#xff0c; 可以通過加計數器或者標記…

二分/雙指針/單調棧隊列專題

1.4924. 矩陣 - AcWing題庫 一開始打表找規律以為是右上角向左下角遞增,但當n很大的時候就不對了,因此我們得去觀察 i * i 100000 * (i - j) j * j i * j 這個式子,我們關心的是這個式子的單調性因此我們可以分別將i和j看作常數來對式子進行求導,可以得到 f(i) 2 * i 10…

Shell $0

個人博客地址&#xff1a;Shell $0 | 一張假鈔的真實世界 我們已經知道在Shell中$0表示Shell腳本的文件名&#xff0c;但在有腳本調用的情形中&#xff0c;子腳本中的$0會是什么值呢&#xff1f;我們通過下面的實例來看。 已測試系統列表&#xff1a; Mac OS X EI Capitan 1…

商品列表及商品詳情展示

前言 本文將展示一段結合 HTML、CSS 和 JavaScript 的代碼&#xff0c;實現了一個簡單的商品展示頁面及商品詳情&#xff0c;涵蓋數據獲取、渲染、搜索及排序等功能。 效果展示 點擊不同的商品會展示對應的商品詳情。 代碼部分 代碼總體實現 <!DOCTYPE html> <htm…

[ VS Code 插件開發 ] 使用 Task ( 任務 ) 代替 createTerminal (終端) 來執行命令

VSCode 官方自己的插件就是這樣執行命令的. 使用體驗 比 默認的終端 好太多了. 重用終端, Shell 集成 , 按任意鍵關閉, 任務是否成功, 左側命令操作 (菜單中功能很多) 等 import * as vscode from vscode; // 執行的命令 let command_str "npm run dev" // 工作目…

大模型綜述一鏡到底(全文八萬字) ——《Large Language Models: A Survey》

論文鏈接&#xff1a;https://arxiv.org/abs/2402.06196 摘要&#xff1a;自2022年11月ChatGPT發布以來&#xff0c;大語言模型&#xff08;LLMs&#xff09;因其在廣泛的自然語言任務上的強大性能而備受關注。正如縮放定律所預測的那樣&#xff0c;大語言模型通過在大量文本數…