(操作系统)C++ 进程描述与控制

实验介绍了Windows任务管理器的使用,C++中使用CreateProcess创建进程,理解进程ID,以及C++11的多线程编程,包括线程创建、join和detach方法。此外,实验还通过信号量解决了进程间的同步问题,如生产者-消费者问题和特定场景下的资源竞争问题。
摘要由CSDN通过智能技术生成

实验一 进程描述与控制

一、实验名称:进程描述与控制

二、实验目的与要求

1. 通过在Window任务管理器中对程序进程进行响应的管理操作,熟悉操作系统进程管理的概念,学习观察操作系统运行的动态性能。

2. 学习C++并发编程中多线程的使用。

3. 简单进程同步问题的复现。

三、实验内容

(一)Window任务管理器的相关操作

启动并进入Windows环境,单击Ctrl + Alt + De键,或者右键单击任务栏,在快捷菜单中单击“任务管理器”命令,打开“任务管理器”窗口。

在本次实验中,你使用的操作系统版本是:

当前机器中有你打开,正在运行的应用程序有:

Windows“任务管理器”的窗口有  7  个选项卡组成,分别是:

(二)编辑并运行下面代码,体会C++并发编程中进程的创建过程,并完成下面问题。

#include"windows.h"

#include<iostream>

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.hWinbase.h KernelWinuser.hWingdi.h。这些头文件定义了Windows的所有资料型态、函数调用、资料结构和常数识别字,是Windows文件中的一个重要部分。

2. CreateProcess()函数的作用是什么?函数原型是什么?请查阅相关资料详细说明。

1) 线程调用CreateProcess时,系统会创建一个进程内核对象,将其引用计数初始化为1(进程内核对象并不是进程本身,它只是操作系统用来管理进程的数据结构,其中包含了进程的一些统计信息)。然后系统为新进程开辟虚拟地址空间,并将可执行文件的代码和数据以及所需的DLL装载到该地址空间中。接着系统为进程主线程创建线程内核对象,并将其引用计数初始为1(同进程一样,线程内核对象也不是线程本身,而且操作系统用来管理线程的数据结构)。主线程将链接器设置的入口点函数作为C/C++运行时启动函数调用,这些启动函数最终又调用代码中的入口点函数如WinMainwWinMainmain 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多线程编程

C++11标准中,<thread>头文件提供了thread类(位于std命令空间中),专门用了完成线程的是创建和使用。

1. 创建线程

一个线程可以用 thread 类的对象来表示,thread类中重载了多种构造函数,最常用的有以下两个:

//1、Fn 表示线程要执行的函数,args 表示向 Fn 传递的多个参数,此构造函数支持泛型

template <class Fn, class... Args>

explicit thread (Fn&& fn, Args&&... args);

//2、移动构造函数

thread (thread&& x) noexcept;

注意,thread 类只提供了移动构造函数,未提供拷贝构造函数。这意味着,我们不能直接将一个事先定义好的 thread 对象赋值给另一个 thread 对象,但可以将临时的(匿名的)thread 对象赋值给另一个 thread 对象。有关移动构造函数,有兴趣同学可阅读《C++11移动构造函数详解》一文做详细了解。

POSIX 标准中,线程所执行函数的参数和返回值都必须为 void* 类型。而 thread 类创建的线程可以执行任意的函数,即不对函数的参数和返回值做具体限定。

举例:

#include <iostream>

#include <thread>

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 <iostream>

#include <mutex>

#include <thread>

#include <chrono>

#include <semaphore.h>



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在其他进程之前获取了自己的信号量,并等待P2P3的信号量释放后才开始执行,最后释放了P4P5的信号量。P2P4之前获取了P3的信号量,并执行完任务后释放了P4的信号量。P3P2之前获取了P1的信号量,并执行完任务后释放了P4的信号量。

2. 用记录型信号量实现生产者-消费者问题。(可以根据自己理解,适当增加不同类型的产品)

源代码为:

#include <iostream>

#include <thread>

#include <windows.h>

#include <ctime>

#include <mutex>

#include <vector>

#include <queue>

#include <condition_variable>

using namespace std;

class Semaphore{

       private:

              mutex mut;

              condition_variable condition;

              int count;

       public:

              Semaphore(int count =0):count(count){};

              void P(){

                     unique_lock<mutex> unique(mut);

                     --count;

                     if(count<0) condition.wait(unique);

              }

       void V(){

                     unique_lock<mutex> 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 <iostream>

#include <mutex>

#include <thread>

#include <chrono>

#include <semaphore>



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_oddn_even分别是奇数和偶数的计数器,它们使用了multiprocessing.Value类型,以允许进程间共享内存。

在生产者进程中,首先等待空缓冲区信号量,然后获取互斥信号量,在缓冲区中查找空单元并将生成的随机整数存入其中。最后释放互斥信号量,并增加满缓冲区信号量。

在取奇数的进程中,首先等待满缓冲区信号量,然后获取互斥信号量,在缓冲区中查找奇数,并将其移出。然后释放互斥信号量,并增加空缓冲区信号量和奇数计数器。

在取偶数的进程中,与取奇数的进程类似。

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值