操作系统课设-Windows 线程的互斥与同步

该文详细介绍了Windows环境下使用线程互斥和信号量解决生产者消费者问题的实验。通过创建Mutex和Semaphore对象,控制生产者和消费者的线程同步,确保对缓冲区的访问正确。实验中展示了如何创建线程,以及使用WaitForSingleObject等函数进行线程间的协调。当修改信号量的初始计数时,可能导致生产者和消费者相互等待,从而影响程序执行流程。
摘要由CSDN通过智能技术生成

一、实验四

Windows 线程的互斥与同步

二、实验内容

创建一个 “Win32 Consol Application” 工程,然后拷贝清单 中的程序,编译成可执行
文件。
/*  实验四源程序 */
//清单4-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;
}

结果显示:

 代码学习:

1.类型学习

HANDLE 是 Windows API 的一种数据类型,用于表示对象的句柄(handle)。在 Windows 操作系统中,大量的资源都是以对象的形式存在的,如文件、进程、线程、事件、互斥体等,而这些对象都可以通过句柄来引用和操作。

HANDLE 类型实际上是一个指向对象的指针,但是不同于普通指针,它并不直接指向对象的实际内存地址,而是由操作系统维护的一个句柄表(handle table)来管理。在 Windows 内核中,每个进程都有一个独立的句柄表,它用于存储该进程所拥有的所有对象的句柄,因此不同进程中可能会有相同的句柄值,但实际表示的对象不同。

DWORD 是 Windows API 中的数据类型之一,表示双字(double word)类型,即 32 位无符号整型。在 Windows 操作系统中,许多 Win32 API 函数使用 DWORD 类型作为参数或返回值。

WINAPI是一个宏定义,用于指定函数调用的约定方式。在 Windows 操作系统中,通常使用的是 stdcall 约定,即在函数调用时参数从右往左依次压入堆栈,由被调用函数负责清理堆栈,返回值保存在 EAX 寄存器中。

LPVOID是指向 void 类型的指针,也就是通用指针类型。在 Windows API 中经常使用该类型来传递指针或者内存地址。

2.函数学习

  • CreateMutex() 函数:在主函数中调用该函数创建了一个互斥对象 Mutex。这个互斥对象用于线程间控制对共享资源的独占访问。

    • lpMutexAttributes 参数指定了安全属性,这里设置为 NULL 表示使用默认安全性。
    • bInitialOwner 参数指定初始所有权,这里设置为 FALSE 表示互斥对象最初没有被任何线程所占有。
    • lpName 参数指定对象名称,这里设置为 NULL 表示不使用名称。
  • CreateSemaphore() 函数:在主函数中调用该函数创建了两个信号量对象 EmptySemaphoreFullSemaphore。这两个信号量对象分别表示缓冲区空闲和满的状态。

    • lpSemaphoreAttributes 参数指定了安全属性,这里设置为 NULL 表示使用默认安全性。
    • lInitialCount 参数指定了初始计数器值,对于 EmptySemaphore,初始计数器值为 SIZE_OF_BUFFER,即缓冲区的大小;对于 FullSemaphore,初始计数器值为 0,表示缓冲区中没有产品可以被消费。
    • lMaximumCount 参数指定计数器允许的最大值,这里设置为 SIZE_OF_BUFFER,即与缓冲区的大小相同。
    • lpName 参数指定对象名称,这里设置为 NULL 表示不使用名称。
  • CreateThread() 函数:在主函数中调用该函数创建了多个生产者和消费者线程。对于每个线程,该函数返回一个句柄 hThreads[i] 和一个标识符 producerID[i]consumerID[i]

    • lpThreadAttributes 参数指定了安全属性,这里设置为 NULL 表示使用默认安全性。
    • dwStackSize 参数指定线程堆栈大小,这里设置为 0 表示使用默认值。
    • lpStartAddress 参数指定线程入口函数地址,这里分别是 Producer() 和 Consumer()
    • lpParameter 参数传递给线程入口函数的参数,这里设置为 NULL 表示没有参数需要传递。
    • dwCreationFlags 参数指定了创建标志,这里设置为 0 表示创建线程立即开始运行。
    • lpThreadId 参数返回线程标识符,这里使用数组 producerID[] 和 consumerID[] 分别保存了生产者和消费者线程的标识符。
  • WaitForSingleObject() 函数:在生产者和消费者线程中使用该函数等待临界区对象的状态变为 signaled。具体地,生产者线程使用该函数等待信号量 EmptySemaphore 变为 signaled;消费者线程使用该函数等待信号量 FullSemaphore 变为 signaled,以及互斥对象 Mutex 变为 nonsignaled。

    • hHandle 参数是等待的对象句柄,这里分别是 EmptySemaphoreFullSemaphore 和 Mutex
    • dwMilliseconds 参数指定最大等待时间,这里设置为 INFINITE 表示无限等待。在有限的等待时间内如果对象状态未变为 signaled,则该函数将返回 WAIT_TIMEOUT。
  • ReleaseMutex() 函数:在生产者和消费者线程中使用该函数释放对临界区的独占访问。具体地,生产者线程使用该函数释放互斥对象 Mutex;消费者线程使用该函数释放互斥对象 Mutex

    • hMutex 参数为互斥对象句柄,这里分别是 Mutex
  • ReleaseSemaphore() 函数:在生产者和消费者线程中使用该函数增加信号量的计数器值。具体地,生产者线程使用该函数增加信号量 FullSemaphore 的计数器值;消费者线程使用该函数增加信号量 EmptySemaphore 的计数器值。

    • hSemaphore 参数为信号量句柄,这里分别是 EmptySemaphoreFullSemaphore

    • lReleaseCount 参数指定增加的计数器数量,这里均设置为 1。

    • 通过以上函数的应用,可以实现多个生产者和消费者线程之间对缓冲区的协调和通信,避免了竞态条件和死锁问题。其中互斥对象 Mutex 用于控制对共享资源的独占访问,信号量 EmptySemaphoreFullSemaphore 分别表示缓冲区空闲和满的状态。同时,还利用了等待和释放对象的功能以及线程睡眠的特性,实现了生产者和消费者的同步和间隔生产/消费的效果。

    • lpPreviousCount 参数返回原来的计数器值,这里不需要使用,设置为 NULL。

  • Sleep() 函数:在生产者线程中使用该函数使当前线程睡眠一段时间,模拟生产过程。

    • dwMilliseconds 参数指定睡眠时间,这里设置为 1000ms。

结果分析:

这段代码的输出有一个特点,就是生产的时候输出结果前换行一次--std::cout << std::endl<< "Producing " << ++ProductID << " ... ";,这样就方便了观察,就是每次生产都会另起一段,如图

 这个结果可以看成四段,每一段都对应一次生产消费过程,当生产者速度快时,生产会更加频繁,理论上段落长度也就越短(消费者出现的频率变低),当然这里由于库存比较少并且库存满后生产者会等待消费者,所以两者会趋于平衡(不管两者的进程数如何改变)。

修改:

	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);

按照注释所说将代码修改后,生产者将无法进行生产,消费者也无法进行消费。原因是生产者需要等待EmptySemaphore为signaled,即仓库有空位;消费者需要等待FullSemaphore为signaled,即仓库有库存。这里两者同时不成立,相互等待。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值