程序员开发实例大全宝库

网站首页 > 编程文章 正文

VC|利用多线程、及线程同步技术实现火车站售票系统模拟程序

zazugpt 2024-08-29 02:17:37 编程文章 16 ℃ 0 评论

Windows操作系统是多任务操作系统,可以同时运行多个任务(应用程序)。

程序是计算机指令的集合,它以文件的形式存储在磁盘上。

进程通常被定义为一个正在运行的程序的实例,是一个程序在其自身的地址空间中的一次执行活动。

一个程序可以对应多个进程。例如可以同时打开多个记事本进程,同时,在一个进程中也可以同时访问多个程序。

进程是资源申请、调度和独立运行的单位,因此,它使用系统中的运行资源;而程序不能申请系统资源,不能被系统调度,也不能作为独立的运行的单位,因此,他不占用系统的运行资源。

进程由两个部分组成:

I 操作系统用来管理进程的内核对象。内核对象是操作系统内部分配的一个内存块,内核对象也是系统用来存放关于进程的统计信息的地方。

II 地址空间。它包含所有可执行模块或DLL模块的代码和数据。他还包含动态内存分配的空间。如线程堆栈和堆分配空间。

那一个程序(进程)可不可以“同时”进行多个任务?那就是多线程程序。

每个进程至少拥有一个线程,来执行进程的地址空间中的代码。当创建一个进程时,操作系统会自动创建这个进程的一个线程,称为主线程。此后,该线程可以创建其他的线程。

单个进程可能包含若干个线程,这些线程都“同时”执行进程地址空间中的代码。

进程是不活泼的。进程从来不执行任何东西,它只是线程的容器。若要使进程完成某项操作,它必须拥有一个在它的环境中运行的线程,此线程负责执行包含在进程的地址空间中的代码。

线程总是在某个进程环境中创建。系统从进程的地址空间中分配内存,供线程的堆栈使用。新线程运行的进程环境与创建线程的环境相同。因此,新线程可以访问进程的内核对象的所有句柄、进程中的所有内存和在这个相同的进程中的所有其他线程的堆栈。这使得单个进程中的多个线程确实能够非常容易的互相通信。

线程有两个部分组成:

I 线程的内核对象,操作系统用它来对线程实施管理,内核对象也是系统用来存放线程统计信息的地方。

II 线程堆栈,它用于维护线程在执行代码时需要的所有参数和局部变量。

当创建线程时,系统创建一个线程内核对象。该线程内核对象不是线程本身,而是操作系统用来管理线程的较小的数据结构。可以将线程内核对象视为由关于线程的统计信息组成的一个小型数据结构。

线程只有一个内核对象和一个堆栈,保留的记录很少,因此所需要的内存也很少。

因为线程需要的开销比进程少,因此在编程中经常采用多线程来解决编程问题,而尽量避免创建新的进程。

操作系统为每一个运行线程安排一定的CPU时间——时间片。系统通过一种循环的方式为线程提供时间片,线程在自己的时间内运行,因时间片相当短,因此,给用户的感觉,就好像线程是同时进行的一样。

线程间通信的方式有两种:全局变量和自定义消息。

例如,在一个火车站售票系统模拟程序中可以定义一个全局变童tickets,为了避免多个线程同时访问这个共享资源,就要求在多个线程之间进行一个同步处理,保证一个线程访问共享资源时,其他线程不能访问该资源。必须等到前者完成火车票的销售过程之后,其他线程才能访问该资源。这与我们在商场买衣服时所进行的活动类似,当我们在试衣间进行试衣服这一活动时,其他试衣服的人必须等待,只有等待我们完成了试衣服这一活动并离幵试衣间时,其他人才能进入该试衣间。

线程的同步处理技术主要有三种:

I 利用互斥对象,CreateMutex();
II 利用事件对象,CreateEvent();
III 利用关键代码段(临界区),InitializeCriticalSection();

互斥对象(mutex)被定义后能够确保线程拥有对单个资源的互斥访问权。

事件对象可以用来表示一个对象是否有信号,如果有信号,表示当前对象不被其他线程操作,可以被操作,被操作时,被无信号状态。

临界区是指只允许一个线程在同一时间访问一个资源(关键源代码)的同步对象。

互斥对象和事件对象属于内核对象,利用内核对象进行线程同步,速度较慢,但利用互斥对象和事件对象这样的内核对象,可以在多个进程中的各个线程间进行同步。

关键代码段时工作在用户方式下,同步速度较快,但在使用关键代码段时,很容易进入死锁状态,因为在等待进入关键代码段时无法设定超时值。

1 利用互斥对象

互斥对象(mutex)属于内核对象,它能够确保线程拥有对单个资源的互斥访问权。 互斥对象包含一个使用数量,一个线程ID和一个计数器。其中ID用于标识系统中的哪个线程当前拥有互斥对象,计数器用于指明该线程拥有可斥对象的次数。

为了创建互斥对象,需要调用函数:CreateMutex,该函数可以创建或打开一个命名的或匿名的互斥对象,然后程序就可以利用该互斥对象完成线程间的同步。

#include "windows.h"
#include "iostream.h"
DWORD WINAPI FunProc1(LPVOID lpParameter);
DWORD WINAPI FunProc2(LPVOID lpParameter);
int index=0;
int ticket=100;
HANDLE hMutex;
void main()
{
	HANDLE hThread1;
	HANDLE hThread2;
	hThread1=CreateThread(NULL,0,FunProc1,NULL,0,NULL);
	hThread2=CreateThread(NULL,0,FunProc2,NULL,0,NULL);
	
	hMutex=CreateMutex(NULL,TRUE,"tickets"); //第三个参数必须赋值
	if(hMutex == NULL)
	{
		cout<<"failure of Mutex creating"<<endl;
		return;
	}
	WaitForSingleObject(hMutex,INFINITE);
	ReleaseMutex(hMutex);
	ReleaseMutex(hMutex);
	CloseHandle(hThread1); 
	CloseHandle(hThread2);
	Sleep(4000);
	system("pause");
}
DWORD WINAPI FunProc1(LPVOID lpParameter)
{
	while(TRUE)
	{
		WaitForSingleObject(hMutex,INFINITE);
		if(ticket>0)
		{
			cout<<"ticket 1:"<<ticket--<<endl;
		}
		else 
			break;
		ReleaseMutex(hMutex); //不管是主线程还是兄弟线程,谁拥有,谁释放。
		Sleep(10);
	}
	return 0;
	
}
DWORD WINAPI FunProc2(LPVOID lpParameter)
{
	while(TRUE)
	{
		WaitForSingleObject(hMutex,INFINITE);
		if(ticket>0)
		{
			cout<<"ticket 2:"<<ticket--<<endl;
		}
		else 
			break;
		ReleaseMutex(hMutex); //不管是主线程还是兄弟线程,谁拥有,谁释放。
		Sleep(10);
	}
	return 0;
	
}

2 利用事件对象

事件对象也属于内核对象,它包含以下三个成员:

使用计数;

用于指明该事件是一个自动重置的事件还是一个人工重置的事件的布尔值;

用于指明该事件处于已通知状态还是未通知状态的布尔值。

以下是代码示例:

#include "windows.h"
#include "iostream.h"
int ticket=100;
HANDLE g_hEvent;
DWORD WINAPI Fun1Proc(LPVOID lpParameter);
DWORD WINAPI Fun2Proc(LPVOID lpParameter);
void main()
{
	HANDLE thread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);
	HANDLE thread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL);
	CloseHandle(thread1);
	CloseHandle(thread2);
	g_hEvent=CreateEvent(NULL,FALSE,TRUE,NULL);
	Sleep(4000);
	CloseHandle(g_hEvent);
}
DWORD WINAPI Fun1Proc(LPVOID lpParameter)
{
	WaitForSingleObject(g_hEvent,INFINITE);
	
	while(ticket)
	{
		cout<<"thread1 sells : "<<ticket--<<endl;
		Sleep(1);
		SetEvent(g_hEvent);
	}
	return 0;
}
DWORD WINAPI Fun2Proc(LPVOID lpParameter)
{
	WaitForSingleObject(g_hEvent,INFINITE);
	while(ticket)
	{
		cout<<"thread2 sells : "<<ticket--<<endl;
		Sleep(1);
		// SetEvent(g_hEvent);
	}
	return 0;
}

3 利用关键代码段

关键代码段,也称为临界区,工作在用户方式下。它是指一个小代码段,在代码能够执行前,它必须独占对某些资源的访问权。通常把多线程中访问同一种资源的那部分代码当作关键代码段。

#include "windows.h"
#include "iostream.h"
int ticket=100;
HANDLE g_hEvent;
DWORD WINAPI Fun1Proc(LPVOID lpParameter);
DWORD WINAPI Fun2Proc(LPVOID lpParameter);
CRITICAL_SECTION g_cs;
void main()
{
	HANDLE thread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);
	HANDLE thread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL);
	CloseHandle(thread1);
	CloseHandle(thread2);
	InitializeCriticalSection(&g_cs);
	Sleep(4000);
	DeleteCriticalSection(&g_cs);
}
DWORD WINAPI Fun1Proc(LPVOID lpParameter)
{ 
	while(TRUE)
	{
		EnterCriticalSection(&g_cs);
		if(ticket>0)
		{
			cout<<"thread1 sells : "<<ticket--<<endl;
			Sleep(1);
		}
		else break;
		LeaveCriticalSection(&g_cs);
	}
	return 0;
}
DWORD WINAPI Fun2Proc(LPVOID lpParameter)
{ 
	while(TRUE)
	{
		EnterCriticalSection(&g_cs);
		if(ticket>0)
		{
			cout<<"thread2 sells : "<<ticket--<<endl;
			Sleep(1);
		}
		else break;
		LeaveCriticalSection(&g_cs);
	}
	return 0;
}

-End-

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表