*******************此篇博客用於記錄學習歷程,僅供交流參考
一、課程設計題目及內容
題目:設計一個按照時間片輪轉法實現處理機調度的程序
時間片輪轉法實現處理機調度的程序設計提示如下:
(1)假設系統有n個進程,每個進程用一個進程控制塊(PCB)來代表。進程控制塊的格式如下表所示,且參數意義也相同。
進程名
鏈接指針
到達時間
估計運行時間
進程狀態
(2)按照進程到達的先後順序排成一個循環隊列,設一個隊首指針指向第一個到達進程的首址。另外再設一個當前運行進程指針,指向當前正運行的進程。
(3)執行處理機調度時,首先選擇隊首的第一個進程運行。
(4)由於本題目是模擬實驗,所以對被選中的進程並不實際啓動運行,而只是執行如下操作:1)估計運行時間減1;
2)輸出當前運行進程的名字。
用這兩個操作來模擬進程的一次運行。
(5)進程運行一次後,以後的調度則將當前指針依次下移一個位置,指向下一個進程,即調整當前運行指針指向該進程的鏈接指針所指進程,以指示應運行進程,同時還應判斷該進程的剩餘運行時間是否爲0,若不爲0,則等待下一輪的運行,若該進程的剩餘運行時間爲0,則將該進程的狀態置爲完成狀態“C”,並退出循環隊列。
(6)若就緒隊列不爲空,則重複上述的步驟(4)和(5)直到所有進程都運行完爲止。
(7)在所設計的調度程序中,應包含顯示或打印語句,以便顯示或打印每次選中進程的名稱及運行一次後隊列的變化情況。
二、程序中使用的數據結構及主要符號說明
mtx: 用來進行上鎖跟解鎖的信號量。
sem[]: 用來實現睡眠,發送信號喚醒線程的信號量。
PCB: 表示進程控制塊,具有name, id, arrtime, runningtime, isdone屬性,分別表示的含義在代碼註釋上。
n: 進程數量。
cnt: 用來計數目前完成的進程數量。
nowtime: 時間計數器,表示目前時間。
nowrunning: 用來表示目前正在輪轉的進程。
que: 用來存放請求進程的隊列。
show: 由於queue沒有遍歷操作,所以在輸出隊列元素的時候需要一個臨時隊列來存儲數據。
三、程序流程圖和帶有註釋的源程序
程序流程圖:
帶有註釋的源程序截圖:
所用數據:
隊列輸出函數:
線程運行函數:
(當線程運行時間爲0的時候不退出,一直運行模擬進程的操作,通過sem_wait函數來使線程休眠,直到收到信號才喚醒。模擬進程操作爲:估計時間減一以及輸出該進程名稱,當估計運行時間爲0的時候,使目前完成進程數加一,並改變狀態,再退出隊列,而若估計時間不爲0,則重新返回隊尾,再次等待喚醒)
初始化函數:
時間片輪轉法函數:
main函數:
四、測試運行結果截圖:
(中間部分太長跳過)
四、實驗結果分析,實驗收穫和體會
實驗結果分析,實驗收穫:
時間片輪轉法,他的主要思路就是,讓目前在隊列隊首的進程出隊,然後運行一個時間片的時間,如果這個時候進程還在運行,就把他所佔用的資源全部剝奪,讓他進入阻塞狀態,然後進入隊尾。將這些資源再給予下一個隊首元素。如果運行一個時間片時間之後,這個進程完成了,就出隊。這種方法很公平。在課設中,要求我們實現的時間片爲1,也就是每次進程出隊,估計時間減一。完成了課設之後,我對這種處理機調度法更加熟悉了,因爲老師要求我們用多線程實現,所以完成代碼之後,我也對多線程編程更加熟悉了。
實驗中遇到的困難:
1)、我的筆記本電腦在虛擬機上運行Linux系統會很卡,而且經常會出現死機的狀況,這個時候如果實現沒有拍攝快照,那麼就會前功盡棄(已經深有體會好幾次了),因此我一開始是在windows上編寫代碼(windows上的codeblocks能使用不少linux獨有的東西(例如信號量pthread, sem等等),非常強,非常神奇),然後在windows上編寫完成之後,因爲老師要求一定要使用linux演示,於是我把代碼轉移到linux上之後,發現編譯運行之後沒有結果輸出,就讓我近乎崩潰,找了十幾個小時的時間才找到bug(有些東西在windows上能夠運行,在linux卻有問題)。
2)、第一點所說到的bug是ios::sync_with_stdio(false); ,這句代碼原本是用來關閉cin跟cout同步,來節省時間的,(因爲我現役比賽,所以一般在main函數裏都會放上這句話),沒想到這句話成爲我差點崩潰的源泉,在windows上使用之後,能夠很快看到輸出,但是在linux編譯之後卻無法輸出,這就讓我當時找bug百思不得其解,幸好最後註釋之後嘗試編譯了一下,才發現是這裏的問題。
3)、第二個bug是在linux的vscode無法使用pthread的地方,原因是因爲pthread不是linux自帶的庫,需要連接外部靜態庫libpthread.a才能夠使用,但是我在網上找了許久,都沒有找到在vscode中連接這個靜態庫的方法,最後只能通過linux本地終端使用命令:
g++ [xxx].cpp -o [xxx] -lpthread的方法來編譯代碼運行。(-lpthread是連接該靜態庫的方法)
4)、第三個bug是,當時編寫完線程運行代碼之後,在時間片輪轉法中,線程睡眠的地方老是不會被喚醒。於是我當時非常頭疼,只能隨手加了個sleep()想找bug,卻歪打正着,喚醒了進程。在windows編寫的時候,sleep()無論是加在鎖裏面,還是鎖外面,都能夠成功喚醒線程,但是在linux中卻失敗了。再次尋找了半天bug,最後將sleep()放在鎖的外面,才成功了。我猜測可能是因爲在鎖裏線程互斥,所以沒法實現等待子線程完成,再來運行主線程的操作。但是在鎖外,就能通過sleep()來等待子線程完成。
最後附上源代碼:
#pragma GCC optimize(3)
#include <bits/stdc++.h>
#include <pthread.h>
#include <unistd.h>
#include <semaphore.h>
#define ull unsigned long long
#define ll long long
#define pi 3.1415926
#define pll pair<ll,ll>
#define pii pair<int,int>
#define endl '\n'
#define eps 1e-6
using namespace std;
const int inf=0x3f3f3f;
const int maxn=30;
const int mod=998244353;
pthread_mutex_t mtx; //互斥信號量
sem_t sem[maxn]; //linux信號量,用來進行喚醒
//代表進程控制塊的數據結構
struct PCB {
string name; //名稱
int id; //PCB的id
int arrtime; //到達時間
int runningtime; //預計運行時間
bool isdone; //狀態 0:未完成 1:完成
}Pb[maxn];
int n; //進程數
int cnt=0; //目前完成的進程數
int nowtime=0; //目前時間計數
PCB nowrunning;
queue<PCB>que; //隊列
queue<PCB>show; //輸出隊列
//隊列輸出
void showque() {
cout<<"目前隊列(右進左出)的情況爲: ";
while(!que.empty()) {
show.push(que.front());
cout<<que.front().name<<" ";
que.pop();
}
while(!show.empty()) {
que.push(show.front());
show.pop();
}
cout<<endl<<endl;
return;
}
//線程運行函數
void *thyx(void *arg) {
int id=*(int*)arg; //獲取線程id
//當該線程運行時間不爲0
while(Pb[id].runningtime>0) {
sem_wait(&sem[id]); //在這裏休眠,等待該線程的id信號來喚醒
pthread_mutex_lock(&mtx); //加鎖
//執行模擬進程操作
Pb[id].runningtime--; //估計運行時間--
cout<<"目前輪轉的線程爲 : "<<Pb[id].name<<endl<<endl;
sleep(1);
//若估計運行時間爲零
if(Pb[id].runningtime==0) {
cnt++; //目前完成進程數++
Pb[id].isdone=1; //表示該完成
cout<<"進程 "<<Pb[id].name<<" 剩餘估計時間爲: "<<Pb[id].runningtime<<" 運行完成,退出隊列"<<endl<<endl;
que.pop();//不論是否完成,進行輪轉的進程都要出隊
}
else {
//未完成則返回隊尾
que.push(Pb[id]);
que.pop();//不論是否完成,進行輪轉的進程都要出隊
cout<<"進程 "<<Pb[id].name<<" 剩餘估計時間爲: "<<Pb[id].runningtime<<" ,返回隊尾"<<endl<<endl;
}
pthread_mutex_unlock(&mtx); //解鎖
}
void (*fun);
return fun;
}
//初始化
void init() {
cout<<endl<<"隨機生成進程數量: ";
n=rand()%8+3; //n個進程 (3<n<10)
cout<<n<<endl<<endl;
nowtime=0;
for(int i=0;i<n;i++) {
string tname="";
switch(i) {
case 0:
tname='a';
break;
case 1:
tname='b';
break;
case 2:
tname='c';
break;
case 3:
tname='d';
break;
case 4:
tname='e';
break;
case 5:
tname='f';
break;
case 6:
tname='g';
break;
case 7:
tname='h';
break;
case 8:
tname='i';
break;
case 9:
tname='j';
break;
default:
tname='z';
break;
}
Pb[i].name=tname;
}
//隨機生成到達時間以及運行時間
for(int i=0;i<n;i++) {
//將所有信號量初始化
sem_init(&sem[i],0,0);
Pb[i].arrtime=rand()%8+1;
Pb[i].runningtime=rand()%8+1;
Pb[i].id=i;
cout<<"進程 "<<Pb[i].name<<" 到達時間: "<<Pb[i].arrtime<<" 估計運行時間: "<<Pb[i].runningtime<<endl;
}
cout<<endl;
cout<<"***************時間片輪轉法****************"<<endl<<endl;
cout<<"*****************開始運行******************"<<endl<<endl;
}
//時間片輪轉
void timemachine() {
while(true) {
//加鎖
pthread_mutex_lock(&mtx);
//如果所有進程數都完成了
if(cnt==n) break;
cout<<"當前時間爲 : "<<nowtime<<endl;
for(int i=0;i<n;i++) {
if(Pb[i].arrtime==nowtime) {
//將當前時間到達的線程入隊
que.push(Pb[i]);
cout<<endl<<"進程 "<<Pb[i].name<<" 到達"<<",";
cout<<"估計運行時間 : "<<Pb[i].runningtime<<endl;
}
}
//如果隊列非空,則選擇隊首進程喚醒,來運行時間片輪轉法
if(!que.empty()) {
nowrunning=que.front();
//cout<<"1*** "<<nowrunning.name<<" "<<nowrunning.id<<endl;
sem_post(&sem[nowrunning.id]);
}
//輸出隊列
cout<<endl;
showque();
nowtime++;//目前時間++
pthread_mutex_unlock(&mtx); //解鎖
//***一定要在解鎖之後sleep(),在鎖裏sleep()或者不sleep()的話,線程有時候會無法喚醒***
//(原因未知,可能是因爲sleep()要等待子線進程完成再執行主線程
sleep(1);
}
}
int main() {
//Linux裏使用關閉cin cout同步會出現無法輸出的情況
//原因未知(windows運行是ok的但是搬到Linux就不行,這個bug找了幾天,差點崩潰
//ios::sync_with_stdio(false);
//srand((ull)time(NULL));
pthread_t td[maxn]; //線程
pthread_mutex_init(&mtx,NULL); //互斥鎖初始化
init(); //初始化/生成隨機數據
for(int i=0;i<n;i++) {
pthread_create(&td[i],NULL,thyx,&Pb[i].id);
//創建線程,(指向線程標識符的指針,設置線程屬性,運行函數,運行函數的參數)
}
//時間片輪轉
timemachine();
for(int i=0;i<n;i++) {
pthread_join(td[i],NULL);
//一直阻塞主線程,直至所有線程終止
}
cout<<"*******************************************"<<endl;
cout<<endl<<" 所有進程運行完成 ! "<<endl;
return 0;
}