windows多线程系列001 火车站售票系统模拟程序

   

    其实我对多线程这一块一直也不是很熟悉,现在正好在项目中需要用到对多线程的优化问题,因此我对多线程的相关知识点进行了学习和总结,在此和大家一起学习和交流。这个系列主要涉及到进程和线程的概念以及利用互斥对象、事件对象与关键代码段等机制处理线程问题的实例讲解。

1 进程和线程

    首先我们需要对进程和线程的概念做个了解。程序是计算机指令的集合,它以文件的形式存储在磁盘上。而进程通常被定义为一个正在运行的程序的实例,是一个程序在其自身的地址空间中的一次执行活动。一个程序可以对应多个进程。这里我们需要注意进程是资源申请、调度和独立运行的单位,因此它使用系统中的运行资源。进程是由两部分组成(1)操作系统用来管理进程的内核对象(系统用来存放关于进程的统计信息的地方)(2)地址空间(它包含所有可执行模块或DLL模块的代码和数据)。这里我们特别需要注意另外一个知识点,进程地址空间的问题,如果你忽略这个知识点那么你在申请内存时就会麻烦了,很可能申请太大的内存而导致内存申请失败。我们需要明白系统每个进程独立的虚拟地址空间。对于32bit进程来说这个地址空间是4GB,因为对32位指针来说,它能寻址的范围为2的32次,即4GB。而实际上对于4GB的虚拟地址空间中,2GB是内核方式分区,供内核代码、设备驱动程序、设备I/O高速缓存、非页面内存池的分配和进程页面表等使用,而我们程序员真正可以申请到的内存空间地址约为2GB。线程也是由两部分组成(1)线程的内核对象(2)线程栈。一个进程中的所有线程都在该进程的虚拟地址空间中,使用该进程的全局变量和系统资源。操作系统给每个线程分配不同的CPU时间片,在某一个时刻,CPU只执行一个时间片内的线程,多个时间片中的相应线程在CPU内轮流执行,由于每个时间片时间很短,所以对用户来说,仿佛各个线程在计算机中是并行处理的。操作系统是根据线程的优先级来安排CPU的时间,优先级高的线程优先运行,优先级低的线程则继续等待。好了,无聊而非常重要的概念就讲这么多了,接下来我们开始看代码,还是来看线程的创建函数原型以及每个参数

2 CreateThread()函数

HANDLE
WINAPI
CreateThread(
_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
_In_ SIZE_T dwStackSize,
_In_ LPTHREAD_START_ROUTINE lpStartAddress,
_In_opt_ __drv_aliasesMem LPVOID lpParameter,
_In_ DWORD dwCreationFlags,
_Out_opt_ LPDWORD lpThreadId
);
lpThreadAttributes
指向SECURITY_ATTRIBUTES型态的结构的指针。在Windows 98中忽略该参数。在Windows NT中,NULL使用默认安全性,不可以被子线程继承,否则需要定义一个结构体将它的bInheritHandle成员初始化为TRUE
dwStackSize
设置初始栈的大小,以字节为单位,如果为0,那么默认将使用与调用该函数的线程相同的栈空间大小。任何情况下,Windows根据需要动态延长堆栈的大小。
lpStartAddress
指向线程函数的指针,形式:@函数名,函数名称没有限制,但是必须以下列形式声明:
DWORD WINAPI 函数名 (LPVOID lpParam) ,格式不正确将无法调用成功。
//也可以直接调用void类型
//但lpStartAddress要这样通过LPTHREAD_START_ROUTINE转换如: (LPTHREAD_START_ROUTINE)MyVoid
//然后在线程声明为:
void MyVoid()
{
return;
}
lpParameter
向线程函数传递的参数,是一个指向结构的指针,不需传递参数时,为NULL。
dwCreationFlags :线程标志,可取值如下
(1)CREATE_SUSPENDED(0x00000004):创建一个挂起的线程,
(2)0:表示创建后立即激活。
(3)STACK_SIZE_PARAM_IS_A_RESERVATION(0x00010000):dwStackSize参数指定初始的保留堆栈 的大小,否则,dwStackSize指定提交的大小。该标记值在Windows 2000/NT and Windows Me/98/95上不支持。
lpThreadId:保存新线程的id。
返回值:函数成功,返回线程句柄;函数失败返回false。若不想返回线程ID,设置值为NULL。
函数说明:
创建一个线程。
语法:
hThread = CreateThread (&security_attributes, dwStackSize, ThreadProc,pParam, dwFlags, &idThread) ;
一般并不推荐使用 CreateThread函数,而推荐使用RTL库里的System单元中定义的 BeginThread函数,因为这除了能创建一个线程和一个入口函数以外,还增加了几项保护措施。
在MFC程序中,应该调用AfxBeginThread函数,在Visual C++程序中应调用_beginthreadex函数。具体细节就不多讲了,下面送上我们的第一道菜

3 火车站售票系统模拟程序

 

#include <windows.h>
#include <iostream>
using namespace std;

DWORD WINAPI Fun1Proc(LPVOID lpParameter);
DWORD WINAPI Fun2Proc(LPVOID lpParameter);

//int index=0;
int tickets = 100;

void main(){

	HANDLE hThread1;
	HANDLE hThread2;

	hThread1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);
	hThread2 = CreateThread(NULL, 0, Fun2Proc, NULL, 0, NULL);


	//关闭句柄,函数并没有终止新创建的线程,只是表示在主线程中对新创建的线程的引用不感兴趣,因此将它关闭
	CloseHandle(hThread1);
	CloseHandle(hThread2);

	//main 函数主线程等待4秒
	Sleep(4000);
	system("pause");
	return;
}

//线程1的入口函数
DWORD WINAPI Fun1Proc(LPVOID lpParameter){

	while (true)
	{
		if (tickets > 0)
			cout << "thread1 sell ticket" << tickets-- << endl;
		else
			break;
	}
	return 0;
}

//线程2的入口函数
DWORD WINAPI Fun2Proc(LPVOID lpParameter){

	while (true)
	{
		if (tickets > 0)
			cout << "thread2 sell ticket" << tickets-- << endl;
		else
			break;
	}
	return 0;
}

    其实我们在运行代码后就会发现有时候我们会把票0也卖出去,为什么呢?这里需要注意一个问题:就是线程的执行会在之前断开的地方继续执行,而不是从头再执行一次线程函数。前面我们讲过,各个线程可以访问进程中的公共变量,所以使用多线程的过程中需要注意的问题是如何防止两个或两个以上的线程同时访问同一个数据,以免破坏数据的完整性。保证各个线程可以在一起适当的协调工作称为线程之间的同步。所以可以这样猜想当我们在线程1 Fun1Proc()函数的tickets > 0时线程1所占用的时间片刚好用完此时线程2进入if(tickets > 0)执行完售票tickets-- 此时tickets变为0然后紧接着线程1在之前没有执行的地方继续执行,也就是说继续售票tickets-- 这样就售出了0好票了。然而对于火车站这不是开玩笑嘛?那么我们应该如何解决呢?在我们接下来的章节中将会一一道来。


  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

DaveBobo

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

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

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

打赏作者

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

抵扣说明:

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

余额充值