实验一 进程描述与控制
一、实验名称:进程描述与控制
二、实验目的与要求
1. 通过在Window任务管理器中对程序进程进行响应的管理操作,熟悉操作系统进程管理的概念,学习观察操作系统运行的动态性能。
2. 学习C++并发编程中多线程的使用。
3. 简单进程同步问题的复现。
三、实验内容
启动并进入Windows环境,单击Ctrl + Alt + De键,或者右键单击任务栏,在快捷菜单中单击“任务管理器”命令,打开“任务管理器”窗口。
在本次实验中,你使用的操作系统版本是:
当前机器中有你打开,正在运行的应用程序有:
Windows“任务管理器”的窗口有 7 个选项卡组成,分别是:
#include"windows.h"
#include
using namespace std;
int main() {
STARTUPINFO startupInfo = {0};
PROCESS_INFORMATION processInformation = { 0 };
BOOL bSuccess = CreateProcess(
TEXT("C:\\WINDOWS\\system32\\notepad.exe"),NULL,NULL,
NULL,FALSE,NULL,NULL,NULL,&startupInfo,
&processInformation);
if (bSuccess) {
cout << "Process started." << endl<< "Process ID:\t"
<< processInformation.dwProcessId << endl;
}
else {
cout << "Cannot start process!" << endl
<< "Error code:\t" << GetLastError() << endl;
}
return 0;
}
1. windows.h头文件的作用是什么?
windows.h是计算机头文件,包含了其他Windows头文件。
这些头文件中最重要的和最基本的是:Windef.h 、Winnt.h、Winbase.h Kernel、Winuser.h、Wingdi.h。这些头文件定义了Windows的所有资料型态、函数调用、资料结构和常数识别字,是Windows文件中的一个重要部分。
2. CreateProcess()函数的作用是什么?函数原型是什么?请查阅相关资料详细说明。
1) 线程调用CreateProcess时,系统会创建一个进程内核对象,将其引用计数初始化为1(进程内核对象并不是进程本身,它只是操作系统用来管理进程的数据结构,其中包含了进程的一些统计信息)。然后系统为新进程开辟虚拟地址空间,并将可执行文件的代码和数据以及所需的DLL装载到该地址空间中。接着系统为进程主线程创建线程内核对象,并将其引用计数初始为1(同进程一样,线程内核对象也不是线程本身,而且操作系统用来管理线程的数据结构)。主线程将链接器设置的入口点函数作为C/C++运行时启动函数调用,这些启动函数最终又调用代码中的入口点函数如WinMain、wWinMain、main和 wmain。当操作系统成功创建了新的进程和主线程后,CreateProcess返回TRUE。
2) CreateProcess()函数原型:
BOOL CreateProcess(
LPCTSTR lpApplicationName, //可执行程序名
LPTSTR lpCommandLine, //命令行字符串,可以为NULL
LPSECURITY_ATTRIBUTES lpProcessAttributes, //新进程对象的安全属性
LPSECURITY_ATTRIBUTES lpThreadAttributes, //新进程对应的线程安全属性
BOOL blnheritHandles, //指定父进程的对象句柄能否被子进程继承
DWORD dwCreationFlags, //指定创建进程的附加标记,即指定新进程的特性
LPVOID lpEnvironment, //指定新进程使用的环境,NULL表示同父进程的环境
LPCTSTR lpCurrentDirection, //指定子进程当前路径,NULL表示同父进程相同
LPSTARTUPINFO lpStartupInfo, //指定新进程主窗口如何显示
LPPROCESS_INFORMATION lpProcessInformation, //作为返回值使用,是一个指针
);
3. STARTUPINFO、PROCESS_INFORMATION分别是什么?
STARTUPINFO 和 PROCESS_INFORMATION 是传递给 CreateProcess() 函数的结构体参数。
STARTUPINFO 结构体包含了与新进程有关的信息,例如启动窗口外观和标准输入输出流的重定向方式等。
PROCESS_INFORMATION 结构体会被用来存储新进程的诸如进程ID、主线程句柄等信息。
4. 程序功能是什么?执行结果是什么?(请附图说明)
程序使用CreateProcess()函数启动一个记事本程序,并输出新进程的进程ID。如果成功创建了进程,输出将类似于:
C++11标准中,
1. 创建线程
一个线程可以用 thread 类的对象来表示,thread类中重载了多种构造函数,最常用的有以下两个:
//1、Fn 表示线程要执行的函数,args 表示向 Fn 传递的多个参数,此构造函数支持泛型
template
explicit thread (Fn&& fn, Args&&... args);
//2、移动构造函数
thread (thread&& x) noexcept;
注意,thread 类只提供了移动构造函数,未提供拷贝构造函数。这意味着,我们不能直接将一个事先定义好的 thread 对象赋值给另一个 thread 对象,但可以将临时的(匿名的)thread 对象赋值给另一个 thread 对象。有关移动构造函数,有兴趣同学可阅读《C++11移动构造函数详解》一文做详细了解。
POSIX 标准中,线程所执行函数的参数和返回值都必须为 void* 类型。而 thread 类创建的线程可以执行任意的函数,即不对函数的参数和返回值做具体限定。
举例:
#include
#include
using namespace std;
void threadFun1(int n) {
cout << "---thread1 running\n";
cout << "n=" << n << endl;
}
void threadFun2(const char * url) {
cout << "---thread2 running\n";
cout << "url=" << url << endl;
}
int main() {
//调用第 1 种构造函数
thread thread1(threadFun1,10);
//调用移动构造函数
thread thread2 = std::thread(threadFun2,"http://c.biancheng.net");
//阻塞主线程,等待 thread1 线程执行完毕
thread1.join();
//阻塞主线程,等待 thread2 线程执行完毕
thread2.join();
return 0;
}
运行上述代码,执行结果为:
2. 线程的使用
除了 join() 成员函数外,thread 类还提供有很多实用的成员函数,表 1 给大家列出了几个最常用的函数:
表 1 thread 类的常用成员函数 |
|
成员函数 |
功 能 |
get_id() |
获取当前 thread 对象的线程 ID。 |
joinable() |
判断当前线程是否支持调用 join() 成员函数。 |
join() |
阻塞当前 thread 对象所在的线程,直至 thread 对象表示的线程执行完毕后,所在线程才能继续执行。 |
detach() |
将当前线程从调用该函数的线程中分离出去,它们彼此独立执行。 |
swap() |
交换两个线程的状态。 |
注意,每个thread 对象在调用析构函数销毁前,要么调用 join() 函数令主线程等待子线程执行完成,要么调用 detach() 函数将子线程和主线程分离,两者比选其一,否则程序可能存在以下两个问题:
1) 线程占用的资源将无法全部释放,造成内存泄漏;
2) 当主线程执行完成而子线程未执行完时,程序执行将引发异常。
1. 有一组相互合作的进程P1、P2、P3、P4、P5、P6,它们的执行过程必须满足如图所示的同步关系,请使用信号量机制编程实现6个进程之间的直接制约同步关系。
源代码为:
#include
#include
#include
#include
#include
using namespace std;
// 信号量数组,初始值为0
sem_t semaphores[6] = {};
void P1()
{
cout << "P1 is waiting..." << endl;
sem_wait(&semaphores[1]);
sem_wait(&semaphores[2]);
cout << "P1 has started." << endl;
this_thread::sleep_for(chrono::seconds(1));
cout << "P1 has finished." << endl;
}
void P2()
{
cout << "P2 is waiting..." << endl;
sem_wait(&semaphores[3]);
cout << "P2 has started." << endl;
this_thread::sleep_for(chrono::seconds(1));
cout << "P2 has finished." << endl;
sem_post(&semaphores[4]);
}
void P3()
{
cout << "P3 is waiting..." << endl;
sem_wait(&semaphores[1]);
cout << "P3 has started." << endl;
this_thread::sleep_for(chrono::seconds(1));
cout << "P3 has finished." << endl;
sem_post(&semaphores[4]);
}
void P4()
{
cout << "P4 is waiting..." << endl;
sem_wait(&semaphores[2]);
cout << "P4 has started." << endl;
this_thread::sleep_for(chrono::seconds(1));
cout << "P4 has finished." << endl;
sem_post(&semaphores[3]);
sem_post(&semaphores[4]);
}
void P5()
{
cout << "P5 is waiting..." << endl;
sem_wait(&semaphores[3]);
sem_wait(&semaphores[4]);
cout << "P5 has started." << endl;
this_thread::sleep_for(chrono::seconds(1));
cout << "P5 has finished." << endl;
sem_post(&semaphores[5]);
}
void P6()
{
cout << "P6 is waiting..." << endl;
sem_wait(&semaphores[5]);
cout << "P6 has started." << endl;
this_thread::sleep_for(chrono::seconds(1));
cout << "P6 has finished." << endl;
}
int main()
{
// 初始化信号量数组
for (int i = 0; i < 6; i++) {
sem_init(&semaphores[i], 0, 0);
}
sem_post(&semaphores[1]); // P1可以开始执行
// 创建6个线程表示6个进程
thread processes[6] = {
thread(P1),
thread(P2),
thread(P3),
thread(P4),
thread(P5),
thread(P6)
};
// 等待所有线程结束
for (int i = 0; i < 6; i++) {
processes[i].join();
}
// 销毁信号量数组
for (int i = 0; i < 6; i++) {
sem_destroy(&semaphores[i]);
}
return 0;
}
3次执行结果为:
P1 is waiting...
P2 is waiting...
P3 is waiting...
P4 is waiting...
P5 is waiting...
P6 is waiting...
P1 has started.
P3 has started.
P1 has finished.
P4 has started.
P2 has started.
P2 has finished.
P3 has finished.
P4 has finished.
P5 has started.
P5 has finished.
P6 has started.
P6 has finished.
实验结果分析:
P1在其他进程之前获取了自己的信号量,并等待P2和P3的信号量释放后才开始执行,最后释放了P4和P5的信号量。P2在P4之前获取了P3的信号量,并执行完任务后释放了P4的信号量。P3在P2之前获取了P1的信号量,并执行完任务后释放了P4的信号量。
2. 用记录型信号量实现生产者-消费者问题。(可以根据自己理解,适当增加不同类型的产品)
源代码为:
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
class Semaphore{
private:
mutex mut;
condition_variable condition;
int count;
public:
Semaphore(int count =0):count(count){};
void P(){
unique_lock unique(mut);
--count;
if(count<0) condition.wait(unique);
}
void V(){
unique_lock unique(mut);
++count;
if(count<=0) condition.notify_one();
}
};
Semaphore S(10);
Semaphore mutex1(1);
Semaphore empty(10);
Semaphore full(0);
void producer(){
while(1){
empty.P();
mutex1.P();
printf("生产者生产了产品\n");
Sleep(500);
mutex1.V();
full.V();
}
}
void custmer(){
while(1){
full.P();
mutex1.P();
printf("消费者消费了产品\n");
Sleep(500);
mutex1.V();
empty.V();
}
}
int main(){
thread p1(producer);
thread p2(custmer);
p1.join();
p2.join();
return 0;
}
执行结果为:
3. 桌子上有一个盘子,每次只能向其中放入一个水果。爸爸专向盘子中放苹果,妈妈专向盘子中放橘子,儿子专等吃盘子中的橘子,女儿专等吃盘子中的苹果。只有盘子为空时,爸爸或者妈妈才可以向盘子中放一个水果;仅当盘子中有自己需要的水果时,儿子或者女儿可以从盘子中取出。请用记录型信号量实现此问题。
源代码为:
#include
#include
#include
#include
#include
using namespace std;
const int BUFFER_SIZE = 1;
sem_t apple, orange, empty, mutex;
int buffer[BUFFER_SIZE] = {};
int in = 0, out = 0;
void father()
{
while (true) {
this_thread::sleep_for(chrono::seconds(1)); // 放一个水果
sem_wait(&empty);
sem_wait(&mutex);
buffer[in] = 1; // 1 表示苹果
in = (in + 1) % BUFFER_SIZE;
cout << "Father put an apple into the plate." << endl;
sem_post(&mutex);
sem_post(&apple);
}
}
void mother()
{
while (true) {
this_thread::sleep_for(chrono::seconds(2)); // 放一个水果
sem_wait(&empty);
sem_wait(&mutex);
buffer[in] = 2; // 2 表示橘子
in = (in + 1) % BUFFER_SIZE;
cout << "Mother put an orange into the plate." << endl;
sem_post(&mutex);
sem_post(&orange);
}
}
void son()
{
while (true) {
sem_wait(&orange); // 取一只橘子
sem_wait(&mutex);
if (buffer[out] == 2) {
out = (out + 1) % BUFFER_SIZE;
cout << "Son took an orange from the plate." << endl;
}
sem_post(&mutex);
sem_post(&empty);
this_thread::sleep_for(chrono::seconds(1));
}
}
void daughter()
{
while (true) {
sem_wait(&apple); // 取一个苹果
sem_wait(&mutex);
if (buffer[out] == 1) {
out = (out + 1) % BUFFER_SIZE;
cout << "Daughter took an apple from the plate." << endl;
}
sem_post(&mutex);
sem_post(&empty);
this_thread::sleep_for(chrono::seconds(1));
}
}
int main()
{
// 初始化信号量
sem_init(&apple, 0, 0);
sem_init(&orange, 0, 0);
sem_init(&empty, 0, BUFFER_SIZE);
sem_init(&mutex, 0, 1);
// 创建4个线程表示4个角色
thread f(father);
thread m(mother);
thread s(son);
thread d(daughter);
// 等待所有线程结束
f.join();
m.join();
s.join();
d.join();
// 销毁信号量
sem_destroy(&apple);
sem_destroy(&orange);
sem_destroy(&empty);
sem_destroy(&mutex);
return 0;
}
执行结果为:
Father put an apple into the plate.
Mother put an orange into the plate.
Daughter took an apple from the plate.
Son took an orange from the plate.
Father put an apple into the plate.
Mother put an orange into the plate.
Daughter took an apple from the plate.
Son took an orange from the plate.
...
实验结果分析:
4.三个进程P1/P2/P3互斥使用一个包含N(N>0)个单元的缓冲区。
P1:每次用producer()生成一个正整数并用put()送入缓冲区某一空单元中;
P2:每次用getodd()从该缓冲区中取出一个奇数并用countodd()统计奇数个数;
P3:每次用geteven()从缓冲区中取出一个偶数并用counteven()统计偶数个数。
请用信号量机制实现三个进程的同步与互斥活动(要求用代码实现)。
源代码为:
import multiprocessing as mp
import random
buffer = mp.Array('i', [0]*N) # 共享内存缓冲区
n_odd = mp.Value('i', 0) # 奇数计数器
n_even = mp.Value('i', 0) # 偶数计数器
mutex = mp.Semaphore(1) # 互斥信号量
empty_sem = mp.Semaphore(N) # 空缓冲区信号量
full_sem = mp.Semaphore(0) # 满缓冲区信号量
def producer():
while True:
item = random.randint(1,100)
empty_sem.acquire() # P(empty_sem)
mutex.acquire() # P(mutex)
for i in range(N):
if buffer[i] == 0:
buffer[i] = item
break
print("Producer produces", item)
mutex.release() # V(mutex)
full_sem.release() # V(full_sem)
def getodd():
while True:
full_sem.acquire() # P(full_sem)
mutex.acquire() # P(mutex)
for i in range(N):
if buffer[i]%2==1:
item = buffer[i]
buffer[i] = 0
n_odd.value += 1
break
print("Get odd number", item)
mutex.release() # V(mutex)
empty_sem.release() # V(empty_sem)
def geteven():
while True:
full_sem.acquire() # P(full_sem)
mutex.acquire() # P(mutex)
for i in range(N):
if buffer[i]%2==0:
item = buffer[i]
buffer[i] = 0
n_even.value += 1
break
print("Get even number", item)
mutex.release() # V(mutex)
empty_sem.release() # V(empty_sem)
def countodd():
while True:
print("Odd numbers: ", n_odd.value)
def counteven():
while True:
print("Even numbers: ", n_even.value)
if __name__ == '__main__':
p1 = mp.Process(target=producer)
p2 = mp.Process(target=getodd)
p3 = mp.Process(target=geteven)
p4 = mp.Process(target=countodd)
p5 = mp.Process(target=counteven)
p1.start()
p2.start()
p3.start()
p4.start()
p5.start()
执行结果为:
由于有随机数,每次执行结果的数据可能不同,但模式如下
Producer produces 69
Get odd number 69
Producer produces 51
Get odd number 51
Producer produces 78
Get even number 78
Producer produces 80
Get even number 80
Get odd number 21
Odd numbers: 3
Producer produces 19
Get odd number 19
Get even number 96
Even numbers: 2
...
实验结果分析:
在该实现中,使用了三个信号量:mutex用于进程间的互斥操作,empty_sem表示缓冲区是否为空,full_sem表示缓冲区是否已满。n_odd和n_even分别是奇数和偶数的计数器,它们使用了multiprocessing.Value类型,以允许进程间共享内存。
在生产者进程中,首先等待空缓冲区信号量,然后获取互斥信号量,在缓冲区中查找空单元并将生成的随机整数存入其中。最后释放互斥信号量,并增加满缓冲区信号量。
在取奇数的进程中,首先等待满缓冲区信号量,然后获取互斥信号量,在缓冲区中查找奇数,并将其移出。然后释放互斥信号量,并增加空缓冲区信号量和奇数计数器。
在取偶数的进程中,与取奇数的进程类似。