实验二:并发与调度

实验二:并发与调度

1.实验目的:

掌在本实验中,通过对事件和互斥体对象的了解,来加深对Windows进程、线程同步和互斥的理解。
(1) 回顾系统进程、线程的有关概念,加深对Windows进程、线程的理解。
(2) 了解事件和互斥体对象。
(3) 通过分析实验程序,了解管理事件对象的API。
(4) 了解在进程中如何使用事件对象。
(5) 了解在进程中如何使用互斥体对象。
(6) 了解父进程创建子进程的程序设计方法。

2.实验内容:

进程间的同步,线程间的互斥。

3.实验步骤:

程序一:进程间的同步

实验描述:

(1)父进程和子进程使用相同的程序,通过命令行中的”child”识别子进程;
(2)父进程创建一个人工复位事件对象,初始状态是非信号状态,然后创建子进程,利用WaitForSingleObject()无限等待事件;
(3)父进程在一个无限循环中等待事件,获取到事件后输出提示;
(4)子进程打开事件对象,利用SetEvent()发送事件,从而使父进程接收到事件。

代码实现:

// 实验二同步.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
# include <windows.h>
# include <iostream>

// 事件名称
static LPCTSTR g_szContinueEvent ="event.Continue";


// 创建子进程,与父进程执行相同的程序。
BOOL CreateChild()
{
	// 获取当前进程的可执行文件名
	TCHAR szFilename[MAX_PATH];
    ::GetModuleFileName(NULL, szFilename, MAX_PATH) ;

	// 格式化用于创建新进程的命令行,包括EXE文件名和字符串“child”。
    TCHAR szCmdLine[MAX_PATH];
    ::sprintf(szCmdLine, "\"%s\" child" , szFilename) ;
	std::cout << szCmdLine << std::endl;

    // 新进程的启动信息结构
    STARTUPINFO si;
    ::ZeroMemory(reinterpret_cast<void*>(&si), sizeof(si)) ;
    si.cb = sizeof(si);		// 本结构的大小

    // 返回的新进程的进程信息结构
    PROCESS_INFORMATION pi;

    // 使用同一可执行文件和带有“child”的命令行创建新进程。
    BOOL bCreateOK = ::CreateProcess(
		szFilename,			// 新进程的可执行文件名
		szCmdLine,        	// 传给新进程的命令行参数
        NULL,				// 缺省的进程安全性
        NULL,				// 缺省的线程安全性
        FALSE,				// 不继承句柄
        CREATE_NEW_CONSOLE,	// 特殊的创建标志
        NULL,				// 新环境
        NULL,				// 当前目录
        &si,				// 启动信息结构
        &pi ) ;				// 返回的进程信息结构

    // 释放对子进程的引用
    if (bCreateOK)
	{
        :: CloseHandle(pi.hProcess);
        :: CloseHandle(pi.hThread);
    }
	return(bCreateOK);
}

// 创建一个事件和一个子进程,然后等待子进程向事件发出信号
void WaitForChild()
{
    // 创建一个事件对象
    HANDLE hEventContinue = ::CreateEvent(
        NULL,				// 缺省的安全性,子进程将具有访问权限
        false,				// 是否是人工复位事件(true 为人工复位)
        FALSE,			    // 事件的初始值是非接受信号状态
        g_szContinueEvent);	// 事件名称,用于父子进程间共享。
    if (hEventContinue != NULL)
	{
		std :: cout << "event created " << std :: endl;
		// 创建子进程
        if (::CreateChild())
        {
			std::cout << " chlid created" << std::endl;

            // 等待,直到子进程发出信号
		    std::cout << "Parent waiting on child." << std::endl;

		    while(1)
		    {
				//p
                ::WaitForSingleObject(hEventContinue, INFINITE);

			    std::cout << "parent received the event signaled from child"
					       << std::endl;
			    //实际应用中可在此进行事件处理。

			    :: Sleep(1000);
			}
        }

        // 清除句柄
        ::CloseHandle(hEventContinue);
        hEventContinue = INVALID_HANDLE_VALUE;
    }
}


// 以下函数在子进程中被调用,其功能是向父进程发出信号
void SignalParent()
{
	std::cout << "child process begining......" << std :: endl;

	// 尝试打开句柄
    HANDLE hEventContinue = ::OpenEvent(
        EVENT_MODIFY_STATE,			// 所要求的最小访问权限
        FALSE,						// 不是可继承的句柄
        g_szContinueEvent);			// 事件名称
    if(hEventContinue != NULL)
	{
	  getchar();


	  //子进程发送事件,通知父进程。(v)
      :: SetEvent(hEventContinue);


	  std :: cout << "event signaled" << std :: endl;

	  getchar();
	  //子进程将事件设置为无信号状态。
	  ResetEvent(hEventContinue);
	  std :: cout << "event reset" << std :: endl;

	  getchar();
    }

    // 清除句柄
    :: CloseHandle(hEventContinue) ;
    hEventContinue = INVALID_HANDLE_VALUE;
}

int main(int argc, char* argv[])
{
    // 检查是父进程还是子进程。
    if (argc>1 && :: strcmp(argv[1] , "child" )== 0)
    {
		// 为子进程,向父进程创建的事件发出信号。
        :: SignalParent();
        std :: cout << "Child released." << std :: endl;
    }
    else
    {
		// 为父进程,创建一个事件和一个子进程,并等待子进程发出信号。
        :: WaitForChild();
        std :: cout << "Parent released." << std :: endl;
    }
    return 0;
}

/*
思考题:
1.本实验中事件是如何在父子进程间共享的?

	// 事件名称
	static LPCTSTR g_szContinueEvent ="event.Continue";

	通过名称来在父子进程间共享事件,事件在父进程和子进程之间同步,子进程发送,父进程获取

2.Windows的哪个API对应事件的P操作?哪个API对应事件的V操作?
	P获取,V释放
    WaitForSingleObject P, SetEvent()  V

3.本实验中是哪个进程向哪个进程发送事件的?

	在不同的进程中,一个进程获取事件,一个进程发送事件。
    子进程向父进程发送,子发--->父做
	

4.获取和发送事件的调用位置有什么特点?
    1、同步配对,有WaitForSingleObject就会有SetEvent


	2、在同一个线程中, 互斥体谁申请,谁使用,谁释放

5.请通过修改程序并对比运行结果来说明Windows的自动复位事件和人工复位事件的区别。
    人工复位(true)会一直响应,
		//子进程将事件设置为无信号状态。
		ResetEvent(hEventContinue);

	自动复位(false)只会响应一次


6.Windows旳事件是计数的吗?
    不是用来计数的,他是一个二进制,只能判断事件有没有发生。


*/

程序二:线程间的互斥

实验描述:

(1) 在主线程中定义线程间共享变量,创建一个互斥体,创建一个加法线程和一个减法线程;
(2) 主线程等待两个线程的结束;
(3) 加法线程和减法线程在互斥体的保护下对共享变量进行操作,操作期间有睡眠,并且循环操作。

代码实现:

// 实验二互斥.cpp : Defines the entry point for the console application.


#include "stdafx.h"
# include <windows.h>
# include <iostream>

HANDLE hMutexValue;

//加法线程和减法线程操作的共享变量。
int Value[2];

DWORD WINAPI IncThreadProc(LPVOID lpParam)
{
	int Count;

	//Count = (int)lpParam;
	//Count = (long)lpParam;
	Count = *((int*)(&lpParam));

	while (1)
	{
		// 等待访问数值
		:: WaitForSingleObject(hMutexValue, INFINITE);

		// 改变并显示该值
		Value[0]+=Count;
		Value[1]+=Count;

		std :: cout << "thread:" << :: GetCurrentThreadId()
					<<"  Value[0]:"<<Value[0]<<"  Value[1]:" << Value[1] << std :: endl;

		:: Sleep(2000);

		// 释放互斥信号量
		:: ReleaseMutex(hMutexValue);
	}
	return(0);
}

DWORD WINAPI DecThreadProc(LPVOID lpParam)
{
	int Count;

	//Count = (int)lpParam;
	Count = *((int*)(&lpParam));

	while (1)
	{
		// 等待访问数值
		:: WaitForSingleObject(hMutexValue, INFINITE);

		// 改变并显示该值
		Value[0]-=Count;
		Value[1]-=Count;

		std :: cout << "thread:" << :: GetCurrentThreadId()
					<<"  Value[0]:"<<Value[0]<<"  Value[1]:" << Value[1] << std :: endl;

		:: Sleep(2000);

		// 释放互斥信号量
		:: ReleaseMutex(hMutexValue);
	}
	return(0);
}


int main(int argc, char* argv[])
{
	HANDLE hThreadInc;
    HANDLE hThreadDec;
    DWORD  dwThreadID[2];//用于存储线程的ID

    //共享变量
	Value[0]=100;
	Value[1]=200;

	// 创建互斥体用于访问数值
	hMutexValue=::CreateMutex(
					NULL,	// 缺省的安全性
					FALSE,	// 初始时不拥有。
					        //一个Mutex在没有任何线程拥有的时候处于有信号状态。
					NULL);  // 匿名的,用于线程间。

	//创建加法线程
	hThreadInc=::CreateThread(
					NULL,			// 缺省的安全性
					0,				// 缺省堆栈
					IncThreadProc,	// 线程控制函数。
					(LPVOID)10,     //线程参数,每次加10
					0,				// 无特殊的标志
					&dwThreadID[0]);// 忽略返回的id

	//创建减法线程
    hThreadDec=::CreateThread(
					NULL,			// 缺省的安全性
					0,				// 缺省堆栈
					DecThreadProc,	// 线程控制函数
					(LPVOID)5,      //线程参数,每次减5
					0,				// 无特殊的标志
					&dwThreadID[1]);// 忽略返回的id


	// 依次无限等待两个线程结束。
	// 也可以调用WaitForMultiObject()同时等待多个对象。
	if (hThreadInc != INVALID_HANDLE_VALUE && hThreadDec != INVALID_HANDLE_VALUE)
	{
		//加法进程
	    ::WaitForSingleObject(hThreadInc, INFINITE);
		//减法进程
		::WaitForSingleObject(hThreadDec, INFINITE);
	}

	return 0;
}


/*
思考题:
1.如果main()中没有语句WaitForSingleObject()会出现什么现象?为什么?
    
	主进程直接结束。因为主进程顺序执行到return 0直接结束,而子线程和主进程是组合关系也跟着结束,WaitForSingleObject()则会让主线程挂起,等待其中线程执行完成。


2.为什么两个线程使用同一个句柄来引用互斥体?

	定义了一个全局变量int Value[2]; 作为互斥体

	使两个线程同步

  互斥体是一种特殊的信号量,在给定的时间内,只允许一个线程访问某个资源。在使用互斥体之前,
  必须使用CreatMutex()创建一个互斥体,互斥体是一个全局对象,它可能被其他进程使用。
  为此,当两个进程都打开了使用相同名称的互斥体时,二者引用了相同的互斥体。使用这种方法可以将两个进程同步。
	


3.Windows的哪个API对应互斥体的P操作?哪个API对应互斥体的V操作?
WaitForSingleObject(hMutexValue, INFINITE); P  ReleaseMutex(hMutexValue);  V


4.获取和释放互斥体的调用位置有什么特点?
    在同一个线程中, 互斥体谁申请,谁使用,谁释放。

5.请指出本实验中的共享资源和临界区。
    共享资源value[2]
    临界区:2个加法与减法各一个

	临界区:
			// 改变并显示该值
			Value[0]-=Count;
			Value[1]-=Count;

			std :: cout << "thread:" << :: GetCurrentThreadId()
						<<"  Value[0]:"<<Value[0]<<"  Value[1]:" << Value[1] << std :: endl;

			:: Sleep(2000);


6.两个线程hThreadInc和hThreadDec,如果一个线程运行到:: Sleep()处,
  另一线程很可能运行到哪个地方?处于什么状态?

  占用临界区,睡眠状态,等待另一个进程结束

  WaitForSingleObject()减法线程,阻塞状态

*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mr.史

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值