操作系統課程設計 - 多線程模擬 - 時間片輪轉法實現處理機調度

*******************此篇博客用於記錄學習歷程,僅供交流參考

在這裏插入圖片描述
一、課程設計題目及內容
題目:設計一個按照時間片輪轉法實現處理機調度的程序
時間片輪轉法實現處理機調度的程序設計提示如下:
(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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章