操作系统实验三——进程同步的经典算法实验报告

实验三 进程同步的经典算法

背景知识

Windows 2000提供的常用对象可分成三类:核心应用服务、线程同步和线程间通讯。其中,开发人员可以使用线程同步对象来协调线程和进程的工作,以使其共享信息并执行任务。此类对象包括互锁数据、临界段、事件、互斥体和信号等。

多线程编程中关键的一步是保护所有的共享资源,工具主要有互锁函数、临界段和互斥体等;另一个实质性部分是协调线程使其完成应用程序的任务,为此,可利用内核中的事件对象和信号。

在进程内或进程间实现线程同步的最方便的方法是使用事件对象,这一组内核对象允许一个线程对其受信状态进行直接控制 (见表3-1) 。

而互斥体则是另一个可命名且安全的内核对象,其主要目的是引导对共享资源的访问。拥有单一访问资源的线程创建互斥体,所有想要访问该资源的线程应该在实际执行操作之前获得互斥体,而在访问结束时立即释放互斥体,以允许下一个等待线程获得互斥体,然后接着进行下去。

与事件对象类似,互斥体容易创建、打开、使用并清除。利用CreateMutex() API可创建互斥体,创建时还可以指定一个初始的拥有权标志,通过使用这个标志,只有当线程完成了资源的所有的初始化工作时,才允许创建线程释放互斥体。

表3-1 用于管理事件对象的API

API名称

描述

CreateEvent()

在内核中创建一个新的事件对象。此函数允许有安全性设置、手工还是自动重置的标志以及初始时已接受还是未接受信号状态的标志

OpenEvent()

创建对已经存在的事件对象的引用。此API函数需要名称、继承标志和所需的访问级别

SetEvent()

将手工重置事件转化为已接受信号状态

ResetEvent()

将手工重置事件转化为非接受信号状态

PulseEvent()

将自动重置事件对象转化为已接受信号状态。当系统释放所有的等待它的线程时此种转化立即发生

为了获得互斥体,首先,想要访问调用的线程可使用OpenMutex() API来获得指向对象的句柄;然后,线程将这个句柄提供给一个等待函数。当内核将互斥体对象发送给等待线程时,就表明该线程获得了互斥体的拥有权。当线程获得拥有权时,线程控制了对共享资源的访问——必须设法尽快地放弃互斥体。放弃共享资源时需要在该对象上调用ReleaseMute() API。然后系统负责将互斥体拥有权传递给下一个等待着的线程 (由到达时间决定顺序) 。

1、实验目的

1) 回顾系统进程、线程的有关概念,加深对Windows 2000线程的理解。

2) 了解互斥体对象,通过对生产者消费者等进程间同步与互斥经典算法的实现,加深对P、V原语以及利用P、V原语进行进程间同步与互斥操作的理解。

2、实验内容和步骤

    (1). 生产者消费者问题

步骤1:创建一个“Win32 Consol Application”工程,然后拷贝清单3-1中的程序,编译成可执行文件。

步骤2:在“命令提示符”窗口运行步骤1中生成的可执行文件。运行结果:

范例:E:\课程\os课\os实验\程序\os11\debug>os31

(假设编译生成的可执行文件是os31.exe)

图 1运行截图一

图 2运行截图二

步骤3:仔细阅读源程序,找出创建线程的WINDOWS API函数,回答下列问题:线程的第一个执行函数是什么(从哪里开始执行)?它位于创建线程的API函数的第几个参数中?

DWORD  WINAPI Producer(LPVOID lpPara)    第三个参数

hThreads[i]=CreateThread(NULL,0,Producer,NULL,0,&producerID[i]);_

步骤4:修改清单3-1中的程序,调整生产者线程和消费者线程的个数,使得消费者数目大与生产者,看看结果有何不同。运行结果:

const unsigned short PRODUCERS_COUNT = 3;  //生产者的个数

const unsigned short CONSUMERS_COUNT = 6;  //消费者的个数

图 3调整生产者线程和消费者线程的个数

图 4运行结果截图

图 5运行结果截图

从中你可以得出什么结论:

消费者数目大于生产者时,相对于生产者生产的速率,消费者线程能够更快速地消费产品,因此消费者的等待时间会减少另外,当消费者线程数量多于生产者时,队列中的产品能够更快速地被消费,从而避免了队列的过度堆积和浪费。这可以提高队列的利用率,使其更有效地作为生产者和消费者之间的缓冲区但是,如果生产者线程的生产速率无法跟上消费者线程的消费速率,生产者线程可能会在生成产品之前被阻塞,等待队列中的空间变得可用。这可能导致生产者的效率降低。                                                   

步骤5:修改清单3-1中的程序,按程序注释中的说明修改信号量EmptySemaphore的初始化方法,看看结果有何不同。运行结果:

EmptySemaphore=CreateSemaphore(NULL,SIZE_OF_BUFFER,SIZE_OF_BUFFER,NULL);_改为

EmptySemaphore=CreateSemaphore(NULL,0,SIZE_OF_BUFFER-1,NULL);_

图 6修改信号量

图 7运行结果截图

图 8运行结果截图

由图7和图8可知修改信号以后无法执行

步骤6:根据步骤4的结果,并查看MSDN,回答下列问题

1)CreateMutex中有几个参数,各代表什么含义。

_CreateMutex中有三个参数,各代表的含义如下:                                   

第一个参数是安全属性指针,通常设置为NULL,表示使用默认的安全属性。               

第二个参数如果设置为TRUE,则互斥对象将被初始化为有信号状态(即可以被获取),如果设置为FALSE,则互斥对象将被初始化为无信号状态(即不可被获取)。这里设置为FALSE,表示在创建时互斥对象不可用。                                                      

第三个参数是互斥对象的名称,如果设置为NULL,则互斥对象是匿名的。               

2)CreateSemaphore中有几个参数,各代表什么含义,信号量的初值在第几个参数中。

CreateSemaphore中有四个参数,各代表的含义如下:                                

第一个参数是安全属性指针,通常设置为NULL,表示使用默认的安全属性。             

第二个参数是信号量的初始值,即信号量对象创建时的值。                           

第三个参数是信号量的最大值,即信号量对象可以达到的最大值。                      

第四个参数是信号量的名称,如果设置为NULL,则信号量是匿名的。

3)程序中P、V原语所对应的实际Windows API函数是什么,写出这几条语句。

WaitForSingleObject(EmptySemaphore,INFINITE); P操作

WaitForSingleObject(Mutex,INFINITE);        

ReleaseMutex(Mutex); V操作

ReleaseSemaphore(FullSemaphore,1,NULL);

4)CreateMutex能用CreateSemaphore替代吗?尝试修改程序3-1,将信号量Mutex完全用CreateSemaphore及相关函数实现。写出要修改的语句:

CreateMutex通常不用CreateSemaphore替代,因为它们是用于不同目的的同步原语。然而,理论上可以使用CreateSemaphore来模拟互斥量(mutex)的行为,但这样做并不常见,也不直观。                                                                        

将信号量Mutex完全用CreateSemaphore及相关函数实现,需要更改的部分如下:       

首先,更改Mutex的创建语句:                                                   

// Mutex = CreateMutex(NULL, FALSE, NULL); // 原始互斥量创建                   

Mutex = CreateSemaphore(NULL, 1, 1, NULL); // 使用信号量模拟互斥量              

然后,在Producer和Consumer线程中,用信号量替换互斥量的操作:                 

生产者线程中:                                                                   

// WaitForSingleObject(Mutex, INFINITE); // 原始互斥量获取                       

WaitForSingleObject(Mutex, INFINITE); // 使用信号量模拟互斥量的获取             

// ... 生产操作 ...                                                             

ReleaseSemaphore(Mutex, 1, NULL); // 释放信号量,模拟互斥量的释放              

消费者线程中同样进行替换。                                                     

注意,由于我们使用信号量来模拟互斥量,所以信号量的初始值和最大值都被设置为1,这样信号量的行为就与互斥量类似了。但是,这样做可能会让代码更难以理解和维护,因为信号量和互斥量在Windows API中有明确的区分。                                     

(2). 读者写者问题

根据实验(1)中所熟悉的P、V原语对应的实际Windows API函数,并参考教材中读者、写者问题的算法原理,尝试利用Windows API函数实现第一类读者写者问题(读者优先)。

具体代码如下:

#include <windows.h>  

#include <stdio.h>  

// 信号量用于控制同时访问的读者数量  

HANDLE hSemReaders = NULL;  

// 事件用于同步写者和读者  

HANDLE hEventWriter = NULL;  

// 计数器用于跟踪当前正在访问的读者数量  

LONG lReaderCount = 0;  

// 互斥量用于保护读者计数器的访问  

HANDLE hMutex = NULL;  

  

// 初始化同步对象  

void Initialize() {  

    hSemReaders = CreateSemaphore(NULL, 1, LONG_MAX, NULL); // 初始时允许一个读者或没有读者  

    hEventWriter = CreateEvent(NULL, TRUE, TRUE, NULL);      // 写者事件初始时置为有信号状态(允许写者)  

    hMutex = CreateMutex(NULL, FALSE, NULL);                 // 互斥量初始时不被任何线程拥有  

  

    if (!hSemReaders || !hEventWriter || !hMutex) {  

        // 处理错误  

        // ...  

    }  

}  

  

// 读者访问共享资源  

void Reader(void) {  

    WaitForSingleObject(hMutex, INFINITE); // 等待互斥量,保护读者计数器  

    if (++lReaderCount == 1) {             // 第一个读者到来时,重置写者事件  

        ResetEvent(hEventWriter);  

    }  

    ReleaseMutex(hMutex); // 释放互斥量  

  

    WaitForSingleObject(hSemReaders, INFINITE); // 等待信号量,允许读者进入  

    // 访问共享资源...  

  

    ReleaseSemaphore(hSemReaders, 1, NULL); // 访问完成后释放信号量  

  

    WaitForSingleObject(hMutex, INFINITE); // 再次等待互斥量,保护读者计数器  

    if (--lReaderCount == 0) {             // 最后一个读者离开时,设置写者事件为有信号状态  

        SetEvent(hEventWriter);  

    }  

    ReleaseMutex(hMutex); // 释放互斥量  

}  

  

// 写者访问共享资源  

void Writer(void) {  

    WaitForSingleObject(hEventWriter, INFINITE); // 等待写者事件,确保没有读者且写者可以进入  

    // 访问共享资源(写操作)...  

  

    // 写操作完成后,重置写者事件,允许新的读者或写者进入  

    ResetEvent(hEventWriter);  

}  

  

// 清理资源  

void Cleanup() {  

    CloseHandle(hSemReaders);  

    CloseHandle(hEventWriter);  

    CloseHandle(hMutex);  

}  

  

int main() {  

    Initialize();   

    // 模拟读者和写者访问共享资源   

    Cleanup();  

    return 0;  

}

  1. 实验结论

通过本次实验,我深入理解了进程与线程的概念、互斥体对象的作用以及P、V原语在进程间同步与互斥操作中的应用。同时,通过实现生产者消费者问题,我加深了对同步与互斥机制的理解,并掌握了在Windows 2000操作系统中使用相关API函数进行编程的方法。    

  1.  程序清单

清单3-1 生产者消费者问题

#include <windows.h>

#include <iostream>

const unsigned short SIZE_OF_BUFFER = 2; //缓冲区长度

unsigned short ProductID = 0;    //产品号

unsigned short ConsumeID = 0;    //将被消耗的产品号

unsigned short in = 0;      //产品进缓冲区时的缓冲区下标

unsigned short out = 0;      //产品出缓冲区时的缓冲区下标

int buffer[SIZE_OF_BUFFER];    //缓冲区是个循环队列

bool p_ccontinue = true;      //控制程序结束

HANDLE Mutex;       //用于线程间的互斥

HANDLE FullSemaphore;     //当缓冲区满时迫使生产者等待

HANDLE EmptySemaphore;     //当缓冲区空时迫使消费者等待

DWORD WINAPI Producer(LPVOID);    //生产者线程

DWORD WINAPI Consumer(LPVOID);    //消费者线程

int main()

{

    //创建各个互斥信号

//注意,互斥信号量和同步信号量的定义方法不同,互斥信号量调用的是CreateMutex函数,同步信号量

//调用的是CreateSemaphore函数,函数的返回值都是句柄。

    Mutex = CreateMutex(NULL,FALSE,NULL);

EmptySemaphore = CreateSemaphore(NULL,SIZE_OF_BUFFER,SIZE_OF_BUFFER,NULL);

    //将上句做如下修改,看看结果会怎样

//EmptySemaphore = CreateSemaphore(NULL,0,SIZE_OF_BUFFER-1,NULL);

    FullSemaphore = CreateSemaphore(NULL,0,SIZE_OF_BUFFER,NULL);

    //调整下面的数值,可以发现,当生产者个数多于消费者个数时,

    //生产速度快,生产者经常等待消费者;反之,消费者经常等待

    const unsigned short PRODUCERS_COUNT = 3;  //生产者的个数

    const unsigned short CONSUMERS_COUNT = 1;  //消费者的个数

    //总的线程数

    const unsigned short THREADS_COUNT = PRODUCERS_COUNT+CONSUMERS_COUNT;

    HANDLE hThreads[THREADS_COUNT]; //各线程的handle

    DWORD producerID[PRODUCERS_COUNT]; //生产者线程的标识符

    DWORD consumerID[CONSUMERS_COUNT]; //消费者线程的标识符

//创建生产者线程

    for (int i=0;i<PRODUCERS_COUNT;++i){

        hThreads[i]=CreateThread(NULL,0,Producer,NULL,0,&producerID[i]);

        if (hThreads[i]==NULL) return -1;

    }

    //创建消费者线程

    for (int i=0;i<CONSUMERS_COUNT;++i){

        hThreads[PRODUCERS_COUNT+i]=CreateThread(NULL,0,Consumer,NULL,0,&consumerID[i]);

        if (hThreads[i]==NULL) return -1;

    }

    while(p_ccontinue){

        if(getchar()){ //按回车后终止程序运行

            p_ccontinue = false;

        }

    }

    return 0;

}

//生产一个产品。简单模拟了一下,仅输出新产品的ID号

void Produce()

{

    std::cout << std::endl<< "Producing " << ++ProductID << " ... ";

    std::cout << "Succeed" << std::endl;

}

//把新生产的产品放入缓冲区

void Append()

{

    std::cerr << "Appending a product ... ";

    buffer[in] = ProductID;

    in = (in+1)%SIZE_OF_BUFFER;

    std::cerr << "Succeed" << std::endl;

    //输出缓冲区当前的状态

    for (int i=0;i<SIZE_OF_BUFFER;++i){

        std::cout << i <<": " << buffer[i];

        if (i==in) std::cout << " <-- 生产";

        if (i==out) std::cout << " <-- 消费";

        std::cout << std::endl;

    }

}

//从缓冲区中取出一个产品

void Take()

{

    std::cerr << "Taking a product ... ";

    ConsumeID = buffer[out];

buffer[out] = 0;

    out = (out+1)%SIZE_OF_BUFFER;

    std::cerr << "Succeed" << std::endl;

    //输出缓冲区当前的状态

    for (int i=0;i<SIZE_OF_BUFFER;++i){

        std::cout << i <<": " << buffer[i];

        if (i==in) std::cout << " <-- 生产";

        if (i==out) std::cout << " <-- 消费";

        std::cout << std::endl;

    }

}

//消耗一个产品

void Consume()

{

    std::cout << "Consuming " << ConsumeID << " ... ";

    std::cout << "Succeed" << std::endl;

}

//生产者

DWORD  WINAPI Producer(LPVOID lpPara)

{

    while(p_ccontinue){

        WaitForSingleObject(EmptySemaphore,INFINITE); //p(empty);

        WaitForSingleObject(Mutex,INFINITE); //p(mutex);

        Produce();

        Append();

        Sleep(1500);

        ReleaseMutex(Mutex); //V(mutex);

        ReleaseSemaphore(FullSemaphore,1,NULL); //V(full);

    }

    return 0;

}

//消费者

DWORD  WINAPI Consumer(LPVOID lpPara)

{

    while(p_ccontinue){

        WaitForSingleObject(FullSemaphore,INFINITE); //P(full);

        WaitForSingleObject(Mutex,INFINITE); //P(mutex);

        Take();

        Consume();

        Sleep(1500);

        ReleaseMutex(Mutex); //V(mutex);

        ReleaseSemaphore(EmptySemaphore,1,NULL); //V(empty);

    }

    return 0;

}

  • 31
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,进程同步操作系统实验中非常重要的一部分,它是为了避免多个进程在访问共享资源时出现竞争和死锁等问题。下面我会介绍一下进程同步经典算法。 1. 互斥锁(Mutex):互斥锁是一种最常用的进程同步机制,它可以保证在同一时刻只有一个进程能够访问共享资源。当一个进程需要访问共享资源时,它会先尝试获取互斥锁,如果锁已经被其他进程占用,则该进程会被阻塞。只有当其他进程释放了互斥锁,该进程才能够继续执行。 2. 信号量(Semaphore):信号量是一种计数器,它可以用来控制多个进程对共享资源的访问。每个信号量都有一个计数值,当计数值为0时,表示共享资源已经被占用,其他进程需要等待。当一个进程访问共享资源时,它会先尝试获取信号量,如果信号量的计数值为0,则该进程会被阻塞。只有当其他进程释放了信号量,计数值增加后,该进程才能够继续执行。 3. 条件变量(Condition Variable):条件变量是一种进程同步机制,它可以让进程在某个条件满足时进行等待。当共享资源的状态发生变化时,它会通知等待在条件变量上的进程。条件变量通常与互斥锁一起使用,以保证多个进程之间的同步。 4. 临界区(Critical Section):临界区是指一段代码,在这段代码中,对共享资源的访问需要进行同步。为了避免多个进程同时进入临界区,可以使用互斥锁等进程同步机制来进行协调。 以上就是进程同步经典算法。在操作系统实验中,可以通过编写相应的程序来实现这些算法,以保证多个进程之间的同步和协调。希望对你有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值