Multi-threaded programming implements the banker's algorithm (the new version of g++11 solves the problem of relatively old CSDN related content)

        There are many articles about multi-threaded programming on CSDN, and the content is also very nice, but the content is a bit old. Today's g++11 version has introduced a new library <thread> for multi-threaded programming, as well as various libraries that match multi-threaded programming. Such as <condition_variable>, <mutex>, etc., how to use these new libraries to write multi-threaded banker's algorithm, the author will try to show you the detailed process and details in this article.

Table of contents

1. Experimental content

2. The purpose of the experiment

3. Design ideas and flow chart

(1) Realization of banker's algorithm

concept:

When implementing the banker's algorithm, some data structures are involved, here is a brief description:

Overall Implementation Framework Description

1. Security Algorithm Ideas

2. Resource request algorithm idea

(2) Realization of multi-threaded program

The new version of multi-threaded programming probably has the following operations:

The callback function mainly realizes the following functions:

Main function implementation:

4. Source program and relevant notes

1. Overall function structure

2. Library and namespace references

3. Define global variables 

4. Detailed explanation of functions

Ⅰ、LessOrNot

Ⅱ、PlusMatrix

Ⅲ、SubMatrix

Ⅳ、Release

5. Detailed initialization function

6. Display function details

7. Detailed security algorithm

8. Detailed explanation of resource allocation algorithm (internal link security algorithm)

9. Multi-threaded callback function

10. Main function

V. Summary


1. Experimental content

Write a multithreaded program to implement the banker's algorithm. Create n threads to apply for or release resources from the bank. The banker will only approve the request if the system is safe. Use the C++11 version to compile and run.

2. The purpose of the experiment

  • Program the banker's algorithm, build a clear framework for the realization of security and allocation algorithms in the banker's algorithm, understand the mechanism of deadlock, and deeply understand the implementation mechanism of the banker's algorithm.
  • Using multi-threaded programming to further deepen the understanding of multi-threading, clarify the mechanism of multi-threading and the problems that need to be paid attention to during runtime, and make analogies and comparisons with parent-child processes to improve understanding of multi-threading and parent-child processes.

3. Design ideas and flow chart

When implementing the relevant content of this experiment, we can divide the overall code into two large frameworks, one framework is used to implement the banker's algorithm, and the other framework is used to implement multi-threaded operations. Next, we will also start from these two frameworks Describe in detail.

(1) Realization of banker's algorithm

concept :

For a resource allocation system with multiple instances of each resource type, the resource allocation graph algorithm is not suitable. Therefore, we use the banker's algorithm, a deadlock avoidance algorithm, to adapt to this type of system (the efficiency is lower than that of the resource allocation graph scheme). The algorithm is so named because it can be used in the banking system, when it does not meet the needs of all customers, the bank will never distribute its cash.

When implementing the banker's algorithm, some data structures are involved , here is a brief description:

1. Availabe: a vector of length m, indicating the number of existing instances of each resource. If Availableabe[j] = k, then there are k existing instances of the resource type Rj.

2. Max: n * m matrix, which defines the maximum demand of each process. If Max[i][j] = k, then process Pi can apply for k instances of resource type Rj at most.

3. Allocation: n * m matrix, which defines the number of instances of various resource types currently allocated by each process. If Allocation[i][j] = k, then process Pi has now allocated k instances of resource type Rj.

4. Need: n * m matrix, indicating the remaining resources needed by each process. If Need[i][j] = k, then process Pi may also apply for k instances of resource type Rj. Note that Need[i][j] = Max[i][j] – Allocation[i][j].

5. request: a vector of length m, indicating the number of resources requested by a certain process. If request[j] = k, it means that the process requests k Rj resources.

The above data structures will be defined as global variables in this experiment program.

Overall Implementation Framework Description

When implementing the banker's algorithm, we mainly need to implement several functions, namely

Security algorithms (algorithms that determine whether a computer is in a secure state)

Resource request algorithm (algorithm to determine whether the request can be safely allowed)

·Initialization algorithm (assign values ​​to each data structure and initialization variable)

Display operation (display the current system status and resource status on the interactive screen)

There is a certain connection between each algorithm, and the banker algorithm framework can be successfully realized with the help of the above functions. The general flow chart is shown in the following figure:

 

Here is a brief description of the security algorithm and resource request algorithm:

1. Security Algorithm Ideas

Ⅰ. Let Work and Finish be vectors with lengths m and n respectively. Initialization is done as follows, Work = Available and for i = 0, 1, … ,n-1, Finish[i] = false.

Ⅱ. Find such i to satisfy:

a.Finish[i] = false

b.Needi <=Work

If no such i exists, then jump to IV.

Ⅲ、Work = Work + Allocationi

Finish[i] = true

Return to II.

IV. If Finish[i] = true for all i, then the system is in a safe state.

In short, for the current state of the system, can we find a reasonable order to complete the allocation and recycling of resources, if so, it means that the system is safe, otherwise the system is not safe.

2. Resource request algorithm idea

Let Requesti be the request vector of process Pi. If Requesti[j] == k, then process Pi requires k instances of resource type Rj. When process Pi makes a resource request, the following actions are taken.

Ⅰ. If Requesti <= Needi, then go to step Ⅱ. Otherwise, an error condition is generated because process Pi has exceeded its maximum request.

Ⅱ. If Requesti <=Availabe, then go to step Ⅲ. Otherwise, Pi has to wait because there are no resources available.

III. Assume that the system can allocate the resource requested by process Pi, and modify the state as follows:

Availabe = Availabe - Requesti ;

Allocation = Allocation + Request ;

Needi = Needi - Requesti ;

If the resulting resource allocation state is safe, then the transaction is complete and process Pi can be allocated the resources it needs. However, if the new state is not safe, then the process Pi must wait for Requesti and restore the original resource allocation state.

In short, it means that when a process sends a request, the program will simulate the state of resource allocation and hand it over to the security algorithm to judge whether the state is safe. If it is safe, then update the resource state to the state after allocation. If If it is not safe, it will not be updated.

When implementing this step, because we define the data structure as a global variable, in order not to perform cumbersome operations on the global variable, we will also use a temporary variable to store the existing state and operate on the temporary variable. If the new state is safe, then Update the global variable to the new state, otherwise don't update the state.

After completing the two most important steps, the rest of the initialization and display operations will come naturally, without much difficulty, so I won’t repeat them here.

(2) Realization of multi-threaded program

Because my g++ version is updated to g++11, in g++11, a class of library has been packaged to deal with multi-thread programming. The name of the library is <thread>. Some old versions of the library before, such as <pthread.h> library, etc., the system prompts that it cannot be used, so here we will use the new version of the multi-thread library to explain the ideas.

The new version of multi-threaded programming probably has the following operations:

1. Based on <thread> library

Implements initialization thread operations.

//Initialize the thread and specify the callback function

thread thread_name (funtion_name, funtion_parameters【if any】);

//The callback function is not specified during initialization, and the callback function needs to be specified later

thread_name = thread(funtion_name, funtion_parameters【if any】);

or

thread_name = thread([&]() { define callback function content});

//Wait for the child thread to end

thread_name.join();

2. Based on <mutex> library

Implements a mutex operation.

//Initialize the mutex

mutex mutex_name;

// lock operation

mutex_name.lock();

//unlock operation

mutex_name.unlock();

3. Based on <condition_variable> library

Implement condition variables and wait and wakeup operations.

Note: Condition variables must be used with unique_lock locks!

//Initialize unique_lock

unique_lock<mutex> unique_lock_name(mutex_name);

//Define condition variables

condition_variable conditon_variable_name;

// wait for condition variable

condition_variable_name.wait(unique_lock_name, bool type variable);

condition_variable_name.wait_for();//Waiting for the end of the time slice or being woken up

condition_variable_name.wait_until();//Wait for the time point or be woken up

//Wakeup operation of condition variable

condition_variable_name.notify_one(); //Wake up a waiting thread, but not necessarily which one

condition_variable_name.notify_all(); //Wake up all waiting threads

4. About this_thread

this_thread is a class with 4 functional functions, as follows:

get_id();//Get the thread number of the current thread

yield();//Give up thread execution and return to the ready state

sleep_for ();//How long does the thread sleep

[eg. this_thread::sleep_for(chrono::seconds(t)), need to link <chrono> library]

sleep_until();//When does the thread sleep until

You can check the specific details again to improve your understanding.

When implementing multi-threading, you first need to determine the callback function, and each thread will call the callback function.

The callback function mainly realizes the following functions:

  • Close the mutex before processing.
  • Use random numbers to generate a request matrix, and the request matrix cannot exceed the Need matrix of this thread.
  • Pass the request matrix into the resource allocation algorithm to judge whether the request can guarantee system security.
  • If the system security can be guaranteed, resources are allocated and unlocked; if the thread has obtained all resources at this time, all resources of the thread are released and another waiting program is called.
  • If the system security cannot be guaranteed, the thread is blocked, and other threads are allowed to execute first, and the blocked thread waits to be awakened.

The flow chart is roughly as follows:

Main function implementation:

Because the number of processes and the number of resources are defined as global variables in advance, a thread array is used when creating a thread, the size of which is the number of processes, and each thread in the array is created. At the same time, each thread sleeps for one second after creation. clock to prevent conflicts between threads.

After the creation is completed, the join() function is also adopted for each thread to wait for the completion of the thread, and then print the prompt that all the thread resources have been allocated, and the whole program ends.

The relationship between threads and the main program is as follows:

 

So far, all the ideas of the program have been described, and the source code will be attached and displayed below.

4. Source program and relevant notes

The whole part of the code is relatively long, and it will be shown in sections here——

1. Overall function structure

Everyone first has a framework understanding of the program we want to implement through this picture~~

We will paste and display the code for each part later.

 

2. Library and namespace references

#include<iostream> 
#include<thread>  //线程库
#include<chrono>  //时间库,用于休眠函数
#include<condition_variable>  //条件变量库
#include<mutex>  //互斥锁库
#include<vector>  //数组库
#include<stdlib.h>  //时间种子库,调用srand()函数,用于刷新时间

using namespace std;

3. Define global variables 

//define global variables
int ResourcesNum = 0;  //定义资源数目,在Init()中进行赋值操作
int ProcessesNum = 0;  //定义进程数目,在Init()中进行赋值操作
vector< vector<int> > Allocation(10, vector<int> (10, 0));  //定义分配矩阵
vector< vector<int> > Max(10, vector<int> (10, 0));  //定义各进程所需的最大资源矩阵
vector< vector<int> > Need(10, vector<int> (10, 0));  //定义各进程仍需的资源矩阵
vector<int> Available(10, 0);  //定义此时空闲资源矩阵

//define count nums
int Afinish = 0;  //定义系统中已完成分配的进程数目
int wait = 0;  //定义系统中无法完成分配而被迫挂起等待的进程数目
//以上两个变量是为了确定按照系统随机数生成的请求矩阵分配资源后还能不能正常回收
//如果不能正常回收,即Afinish+wait == ProcessesNum,那么就立即停止系统运行,即使止损
//该两个变量用在回调函数Multi_Process中

//Define mutex lock
mutex mtx;  //定义互斥锁
condition_variable cv1;  //定义条件变量用于挂起和唤醒

4. Detailed explanation of functions

Ⅰ、LessOrNot

//判断一个矩阵中的每一项是否都小于另一个矩阵的每一项
bool LessOrNot(vector<int> target1, vector<int> target2, int length) {
	for(int i = 0; i < length; i++) {
		if(target1[i] > target2[i]) return false;
	}
	return true;
}

Ⅱ、PlusMatrix

//将两个矩阵相加
void PlusMatrix(vector<int> &target1, vector<int> target2, int length) {
	for(int i = 0; i < length; i++) {
		target1[i] = target1[i] + target2[i];
	}
} 

Ⅲ、SubMatrix

//将两个矩阵相减
void SubMatrix(vector<int> &target1, vector<int> target2, int length) {
	for(int i = 0; i < length; i++) {
		target1[i] = target1[i] - target2[i];
	}
}

Ⅳ、Release

//将进程占据的资源全部释放,用于该进程已经完成的情况
void Release(int id) {
	PlusMatrix(Available, Allocation[id], ResourcesNum);
	return ;
} 

5. Detailed initialization function

/*初始化函数*/
void Init() {  
	cout << "********************************************" << endl;
	cout << "Written  by  wyr" << endl;
	cout << "Please respect originality." << endl;
	cout << "********************************************" << endl;
	cout << endl;
	cout << "Please enter the number of resources(1~10):" << endl;  //输入资源总数
	cin >> ResourcesNum;
	cout << "Please enter the number of processes(1~10):" << endl;  //输入进程总数
	cin >> ProcessesNum;
	cout << "Please enter the maximum number of various resources required by each process in turn:" << endl;  //输入各个进程所需要的各种资源的最大数目
	for(int i = 0; i < ProcessesNum; i++) {
	    for(int j = 0; j < ResourcesNum; j++) {
			cin >> Max[i][j];//Input the maximum demand matrix
	}
  }
	cout << "Please enter the number of various resources that have been allocated to each process in turn" << endl;//输入各个进程已被分配的各种资源的数目
        for(int i = 0; i < ProcessesNum; i++) {
            for(int j = 0; j < ResourcesNum; j++) {
                        cin >> Allocation[i][j];//Input distribution matrix
        }
  }
	cout << "Please enter the available quantity of various resources" << endl;  //输入现在空闲可使用的资源数目
	for(int i = 0; i < ResourcesNum; i++) {
		cin >> Available[i];//input the available matrix
  }
        for(int i = 0; i < ProcessesNum; i++) {//根据输入的Max矩阵和Allocation矩阵计算Need矩阵
            for(int j = 0; j < ResourcesNum; j++) {
                        Need[i][j] = Max[i][j] - Allocation[i][j];
        }
  }
}

6. Display function details

/*显示函数*/
void Show() {
	cout << "---------------------------------------------------------------------------------" << endl;
	cout << "Current state is as follows:" << endl;
	//打印分配矩阵
	cout << "____Allocation____";
	for(int i = 0; i < ProcessesNum; i++) {
		cout << endl;
		cout<<"P"<<i<<"  ";
	    for(int j = 0; j < ResourcesNum; j++) {
		    cout << Allocation[i][j] << "  " ;
	}
}
	//打印Max矩阵
	cout << endl;
        cout << "____Max____";
        for(int i = 0; i < ProcessesNum; i++) {
                cout << endl;
                cout<<"P"<<i<<"  ";
            for(int j = 0; j < ResourcesNum; j++) {
                    cout << Max[i][j] << "  " ;
        }
}
        cout << endl;
	//打印Need矩阵
 	cout << "____Need____";
        for(int i = 0; i < ProcessesNum; i++) {
                cout << endl;
                cout<<"P"<<i<<"  ";
            for(int j = 0; j < ResourcesNum; j++) {
                    cout << Need[i][j] << "  " ;
        }
}
	cout << endl;
	//打印Available矩阵
	cout << "____Available____" << endl;
	for(int i = 0; i < ResourcesNum; i++) {
		cout << Available[i] << "  ";
	}
	cout << endl << "---------------------------------------------------------------------------------" << endl;
	cout << endl;
}

7. Detailed security algorithm

bool SecurityOrNot(vector<vector<int>> Allocation1, vector<vector<int>> Max1, vector<vector<int>> Need1, vector<int> Available1, int ResourcesNum1, int ProcessesNum1) {
	vector<int> Work = Available1;  //定义Work矩阵,将Availabe矩阵的值赋给他
	vector<int> Finish1(10,0);  //定义Finish矩阵,标记某进程是否已完成
	vector<int> Sequence;  //定义Sequence矩阵,标记进程完成的顺序并用于打印显示
	while(1) {
	int i = 0;
	for(i = 0; i < ProcessesNum1; i++) {
		if(Finish1[i] == 0 && LessOrNot(Need1[i], Work, ResourcesNum1)) {//根据之前思路分析中有关安全性算法的描述,这是在判断有没有一个进程未完成且需求量Need小于空闲量Availabe
			PlusMatrix(Work, Allocation1[i], ResourcesNum1);//如果有,则说明该进程可以完成,并将之前分配给该进程的资源回收
			Finish1[i] = 1;//标记该进程已完成
			Sequence.push_back(i);//将进程放入Sequence矩阵中
			break;//跳出循环,进行下一次循环
		    }
	}
	if(i == ProcessesNum1) {//没有一个进程未完成或需求量Need小于空闲量Availabe,则判断是不是所有进程都执行完毕了。
		for(int j = 0; j < ProcessesNum1; j++) {
			if(Finish1[j] == 0) return false;//如果不是,则说明不能安全回收序列,返回false
		}
		//如果是,就打印出进程的执行顺序,并返回true。
		cout << "该系统状态是安全的,可以找到一个安全序列满足条件,该安全序列为:" << endl;
		for(int i = 0; i < Sequence.size(); i++) {
			cout << "P" << Sequence[i] << "  " ;
		}
		cout << endl;
		return true;
	}
	}
	return true;
}

8. Detailed explanation of resource allocation algorithm (internal link security algorithm)

/*资源分配算法*/
bool ResourceRequest(vector<int> request, int processnum) {
	//定义临时存储矩阵
	vector< vector<int> > tempAllocation = Allocation;
	vector< vector<int> > tempMax = Max;
	vector< vector<int> > tempNeed = Need;
	vector<int> tempAvailable = Available;
	if(LessOrNot(request, tempNeed[processnum], ResourcesNum)) {//如果请求矩阵比进程实际的需求矩阵还要大,那就不予通过,直接跳转到报错三;如果满足条件,进入下一步判断
		if(LessOrNot(request, tempAvailable, ResourcesNum))  {//如果请求矩阵比现有的空闲资源矩阵还要大,那就不予通过,直接跳转到报错二;如果满足条件,进入下一步判断
		    //上述两个判断都通过,则虚拟分配一下资源
			SubMatrix(tempAvailable,request,ResourcesNum);
		    PlusMatrix(tempAllocation[processnum], request,ResourcesNum);
		    SubMatrix(tempNeed[processnum], request,ResourcesNum);
		    if(SecurityOrNot(tempAllocation, tempMax, tempNeed, tempAvailable, ResourcesNum, ProcessesNum)) {//判断分配后的资源是否满足安全性算法,如果不满足,就直接跳转到报错一
			//如果满足,那么就将实际的资源矩阵分配完毕,并提示分配成功
			SubMatrix(Available,request,ResourcesNum);
			PlusMatrix(Allocation[processnum], request,ResourcesNum);
			SubMatrix(Need[processnum], request,ResourcesNum);
			cout << "P" << processnum << "分配状态安全,已完成分配,新的分配状态如下: " <<  endl;
			cout << endl;
			//Show();
			return true;
		    }
		    else {
		    	cout << "P" << processnum << "分配状态不安全,不进行资源分配!" << endl;//报错一
				cout << endl;
				return false;
		    	}
		   }
		else {  
			cout << "P" << processnum << "没有可用资源,请求失败!" << endl;//报错二
			cout << endl;
			return false;
			}
	    }
	    else {
	    	cout << "P" << processnum << "进程已超过其最大请求!" << endl;//报错三
			cout << endl;
			return false;
	    	}
	}	

9. Multi-threaded callback function

/*多线程函数*/
void Multi_Process(int index) {	
	unique_lock<mutex> lock(mtx);//定义unique_lock锁并自动加锁
	int id = index;//进程的编号
	bool NotFinishFlag = 1;//未完成的标志
	while(NotFinishFlag) {
		vector<int> request(ResourcesNum, 0);
		cout << "P" << id << "当前请求资源矩阵为:" ;
		for(int i = 0; i < ResourcesNum; i++) {//随即生成请求矩阵
			if(Need[id][i] == 0) {
				request[i] = 0;  //如果需求矩阵对于该资源的需求量本身为0,那么请求矩阵对该资源的请求自动设为0
				cout << "  " << request[i];
			}
			else {
				do {
				request[i] = rand() % (Need[id][i] + 1);//否则,生成一个不为0的请求
				}while(request[i] == 0);
				cout << "  " << request[i];
			}
		}
		cout << endl;
		bool cur = ResourceRequest(request, id);  //将请求矩阵调入资源分配算法判断是否可以安全运行
		if(cur) {//如果可以安全运行,那么判断进程是否执行完毕
			int i = 0;
			for(i = 0; i < ResourcesNum; i++) {
				if(Need[id][i] != 0) break;
			}
			if(i == ResourcesNum) {
				NotFinishFlag = 0;//标记自己已经完成,可以退出循环,完成线程的任务
				cout << "P" << id << "进程资源分配完毕" << endl;
				Release(id);
				cout << "P" << id << "进程执行完毕" << endl;
				cout << endl;
				Show();
				Afinish++;
				wait--;
				cv1.notify_one();//如果执行完毕,那么就打印成功信息,并且唤醒在等待的进程
				return;
				}
			else {
				//该步处理条件非常重要!!
                cv1.notify_one();//如果进程没有执行完毕,那么释放锁,唤醒一个等待进程
				this_thread::yield();//同时自身完成中止执行,返回到就绪状态,等待下一次执行
			}//写多线程程序的时候,一定需要保证使用notify的线程在使用wait线程之后执行。
			}
		else {
			 wait++;
			 //cout << "wait = " << wait << endl;
			 if(wait + Afinish == ProcessesNum) {//如果等待进程和完成进程数相加刚好为ProcessesNum,说明此时系统陷入死循环,无论作何请求都会发生死锁,此时应该立即终止!
			 	cout << "银行家分配算法达到极限,再分配下去就陷入将陷入死锁状态,就此停止!请手动CTRL+C停止!" << endl;
			 	return;
			  }
			cv1.wait(lock);	//如果进程的请求不满足安全性算法,那么就自动挂起堵塞,等待其他进程唤醒。
		}	
	}
	return ;
}

10. Main function _

int main() {
	srand(time(NULL));//设置时间种子,随时间不同而刷新请求矩阵的值
	Init();//初始化函数,设计输入操作
	Show();//显示初始化信息
	thread thread_pool[ProcessesNum]; //定义ProcessesNum个线程
	for(int i = 0; i < ProcessesNum; i++) {
		thread_pool[i] = thread(Multi_Process, i);//创建线程
		this_thread::sleep_for(chrono::seconds(1));//执行完后睡眠1s
}
	for(int i = 0; i < ProcessesNum; i++) {
		thread_pool[i].join();//让主线程等待所有线程的完成
	}
	
	cout << endl << "***********************************************" << endl;
	cout << "至此,所有线程执行完毕!" << endl;//线程执行完毕
	return 0;
}

V. Summary

I have added detailed comments to the above code lines, and all the codes can be run normally after pasting them together. The main purpose of displaying them here is to provide you with a sample as an exercise, and also to let the teacher review their homework I can run the source code to see if it’s right, hahahaha~

Please note that when running, you can only use the g++ compiler to run in ubuntu, but not on vs. The commands to run at the same time are as follows:

g++ ***.cpp -o *** -lpthread

Be sure to link the last lpthread library, otherwise the compilation will fail!

The final effect is as follows -

After running the program, the initial value is as follows: (This is the initial value I manually set, and you will be prompted to enter the initial value when the program initially runs)

The final running result is as follows:

 

 

The author feels that this program is really difficult, and I hope this blog can be helpful to uu!

Supongo que te gusta

Origin blog.csdn.net/wyr1849089774/article/details/130659503
Recomendado
Clasificación