操作系统课程设计 - 多线程模拟 - 时间片轮转法实现处理机调度

*******************此篇博客用于记录学习历程,仅供交流参考

在这里插入图片描述
一、课程设计题目及内容
题目:设计一个按照时间片轮转法实现处理机调度的程序
时间片轮转法实现处理机调度的程序设计提示如下:
(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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章