孙鑫MFC深入详解 第十五章 多线程(一)

Windows中的多线程程序设计一直是所有编程人员感到困难的一个地方,主要是在Windows多线程程序设计中,往往要考虑很多的东西,线程启用的多少,线程之间的同步问题等等...下面通过模拟火车售票系统来进行讲解

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

using namespace std ;

DWORD WINAPI ThreadProc1(LPVOID lpParameter) ;
DWORD WINAPI ThreadProc2(LPVOID lpParameter) ;

int index ;
int tickets = 100 ;
HANDLE hMutex ;			// 声明一个全局的互斥对象句柄

int main()
{
	HANDLE hThread1 ;
	HANDLE hThread2 ;

	hThread1 = CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL) ;
	hThread2 = CreateThread(NULL, 0, ThreadProc2, NULL, 0, NULL) ;
	CloseHandle(hThread1) ;		// 关闭线程内核对象句柄, 并没有阻止新建线程的结束, 但是将线程内核对象的引用计数减1, 
	CloseHandle(hThread2) ;		// 当新建线程执行完成后, 引用计数也会减1。 当引用计数为0时, 系统就会释放线程内核对象
					// 如果此处不使用CloseHandle()函数关闭新建线程内核对象, 则在该线程执行完成后, 其引用计数也不会为0,
					// 则该线程内核对象会一直存在在系统内核当中, 所以, 在我们创建了一个新线程之后, 如果后面的代码不会
					// 用到此线程内核对象, 则应该调用CloseHandle()函数关闭该线程的线程内核对象

	hMutex = CreateMutex(NULL, FALSE, NULL) ;	// 创建一个不属于当前线程的匿名互斥对象

	Sleep(4000) ;

	return 0 ; 
}

DWORD WINAPI ThreadProc1(LPVOID lpParameter) 
{	
	while (TRUE)
	{
		WaitForSingleObject(hMutex, INFINITE) ;
		if (tickets > 0)
		{
			Sleep(1) ;
			cout << "thread1 sell ticket: " << tickets-- << endl ;
		}
		else 
			break ;

		ReleaseMutex(hMutex) ;		// 释放hMutex互斥对象的所有权
	}

	return 0 ;
}

DWORD WINAPI ThreadProc2(LPVOID lpParameter) 
{
	while (TRUE)
	{
		WaitForSingleObject(hMutex, INFINITE) ;
		if (tickets > 0)
		{
			Sleep(1) ;
			cout << "thread2 sell ticket: " << tickets-- << endl ;
		}
		else 
			break ;

		ReleaseMutex(hMutex) ;
	}
	
	return 0 ;
}

运行之后,结果如下:



我们可以看到线程一和线程二之间交替的执行,直到将票卖完。   那么这样的结果,肯定是缺少不了线程之间的同步,那么Windows程序中,线程之间是如何维持线程直接的同步的呢??   这也主要靠windows操作系统为我们提供的一个内核对象 ------> (互斥对象) 来实现的。下面我们将主要分析程序是如何执行的;

分析:

当我们创建互斥对象(hMutex)时(上述代码中的第26行), 第二个参数传递为FALSE,那么当前就没有线程拥有该互斥对象(hMutex)的所有权,那么操作系统就会将该互斥对象(hMutex)设置为已通知状态,也就是信号态;当程序执行到第一个线程(ThreadProc1)的WaitForSingleObject()函数时,因为此时的互斥对象(hMutex)处于信号态,那么该线程(ThreadProc1)就请求获得了该互斥对象(hMutex)的所有权,那么操作系统就会将该互斥对象(hMutex)的线程ID设置成线程一(ThreadProc1)的线程ID,此时,线程一(ThreadProc1)便拥有了该互斥对象(hMutex),同时,操作系统立即将该互斥对象(hMutex)设置为未通知状态(即非信号态),程序继续向下运行,执行到Sleep()函数时,线程一(ThreadProc1)休眠,这时,操作系统便会选择线程二(ThreadProc2)开始运行,同样执行到WaitForSingleObject()函数,因为此时互斥对象(hMutex)被线程一(ThreadProc1)所拥有,处于未通知状态(即非信号态),这样线程二(ThreadProc2)便不能获得该互斥对象(hMutex)的所有权,WaitForSingleObject()函数便处于等待状态,直到线程1(ThreadProc1)休眠结束,继续执行线程一(ThreadProc1)中的后续代码(即线程一(ThreadProc1)中Sleep()函数后的代码),线程一(ThreadProc1)的工作完成后,我们便要释放线程一(ThreadProc1)对该互斥对象(hMutex)的所有权(即ReleaseMutex()的操作),当我们调用ReleaseMutex()函数时, 操作系统便会将该互斥对象(hMutex)的线程ID置为0并且将该互斥对象(hMutex)设置为已通知状态(即信号态),而此时处于等待状态的线程二(ThreadProc2)便立即请求到该互斥对象(hMutex)的所有权,同时操作系统立即会将互斥对象(hMutex)的线程ID设置为线程二的线程ID并且将其(hMutex)状态转变为未通知状态(即非信号态),线程二(ThreadProc2)中的相关代码继续向下执行,当线程二(ThreadProc2)的工作完成后, 也会调用ReleaseMutex()函数释放线程二(ThreadProc2)对互斥对象(hMutex)的所有权 ----> 这样程序会一直在线程一(ThreadProc1)和线程二(ThreadProc2)之间交替的运行直到数据的处理完毕为止。而且,这样做还能保持线程之间的同步,对数据的处理会更加的安全。


现在应该对互斥对象以及windows是如何通过互斥对象来实现多个线程之间的同步的。


我们知道互斥对象使操作系统维护的一个数据结构,那么该数据结构都包含什么东西呢??

 * 互斥对象

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


可以看到互斥对象除了一个线程ID还有一个计数器,那么,我们再来看看计数器是怎么回事以及它是如何运作的,在此之前,我们先来看一段代码,如下:

MultiThread2.cpp

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

using namespace std ;

DWORD WINAPI ThreadProc1(LPVOID lpParameter) ;
DWORD WINAPI ThreadProc2(LPVOID lpParameter) ;

int index ;
int tickets = 100 ;
HANDLE hMutex ;			// 声明一个全局的互斥对象句柄

int main()
{
	HANDLE hThread1 ;
	HANDLE hThread2 ;

	hThread1 = CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL) ;
	hThread2 = CreateThread(NULL, 0, ThreadProc2, NULL, 0, NULL) ;
	CloseHandle(hThread1) ;			// 关闭线程内核对象句柄, 并没有阻止新建线程的结束, 但是将线程内核对象的引用计数减1, 
	CloseHandle(hThread2) ;			// 当新建线程执行完成后, 引用计数也会减1。 当引用计数为0时, 系统就会释放线程内核对象
						// 如果此处不使用CloseHandle()函数关闭新建线程内核对象, 则在该线程执行完成后, 其引用计数也不会为0,
						// 则该线程内核对象会一直存在在系统内核当中, 所以, 在我们创建了一个新线程之后, 如果后面的代码不会
						// 用到此线程内核对象, 则应该调用CloseHandle()函数关闭该线程的线程内核对象

//	hMutex = CreateMutex(NULL, FALSE, NULL) ;	// 创建一个不属于当前线程的匿名互斥对象

	hMutex = CreateMutex(NULL, TRUE, NULL) ;	// 创建一个属于当前线程的匿名互斥对象
	ReleaseMutex(hMutex) ;				// 释放当前线程对互斥对象的所有权

	Sleep(4000) ;

	return 0 ; 
}

DWORD WINAPI ThreadProc1(LPVOID lpParameter) 
{	
	while (TRUE)
	{
		WaitForSingleObject(hMutex, INFINITE) ;
		if (tickets > 0)
		{
			Sleep(1) ;
			cout << "thread1 sell ticket: " << tickets-- << endl ;
		}
		else 
			break ;

		ReleaseMutex(hMutex) ;		// 释放hMutex互斥对象的所有权
	}

	return 0 ;
}

DWORD WINAPI ThreadProc2(LPVOID lpParameter) 
{
	while (TRUE)
	{
		WaitForSingleObject(hMutex, INFINITE) ;
		if (tickets > 0)
		{
			Sleep(1) ;
			cout << "thread2 sell ticket: " << tickets-- << endl ;
		}
		else 
			break ;

		ReleaseMutex(hMutex) ;
	}
	
	return 0 ;
}

和MultiThread1.cpp的代码差不多,只是略微的修改了一下,将MultiThread1.cpp代码中的第26行注释掉,在第28行重新创建一个互斥对象(注意这两行(26和28)代码的区别,并且在29行又多添加了一行代码,【关于这两行代码的意义,在程序的注释中写的很详细】我们运行程序,看看结果,如下:



程序也是能够正常的执行,分析可以结合MultiThread1.cpp程序的分析,自己研究。   

我们继续向下,MultiThread2.cpp程序也是能够正常的执行,但是如果我们在MultiThread2.cpp程序的第28和第29行之间加上一行代码,完整代码如下:

MultiThread3.cpp

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

using namespace std ;

DWORD WINAPI ThreadProc1(LPVOID lpParameter) ;
DWORD WINAPI ThreadProc2(LPVOID lpParameter) ;

int index ;
int tickets = 100 ;
HANDLE hMutex ;			// 声明一个全局的互斥对象句柄

int main()
{
	HANDLE hThread1 ;
	HANDLE hThread2 ;

	hThread1 = CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL) ;
	hThread2 = CreateThread(NULL, 0, ThreadProc2, NULL, 0, NULL) ;
	CloseHandle(hThread1) ;			// 关闭线程内核对象句柄, 并没有阻止新建线程的结束, 但是将线程内核对象的引用计数减1, 
	CloseHandle(hThread2) ;			// 当新建线程执行完成后, 引用计数也会减1。 当引用计数为0时, 系统就会释放线程内核对象
						// 如果此处不使用CloseHandle()函数关闭新建线程内核对象, 则在该线程执行完成后, 其引用计数也不会为0,
						// 则该线程内核对象会一直存在在系统内核当中, 所以, 在我们创建了一个新线程之后, 如果后面的代码不会
						// 用到此线程内核对象, 则应该调用CloseHandle()函数关闭该线程的线程内核对象

//	hMutex = CreateMutex(NULL, FALSE, NULL) ;	// 创建一个不属于当前线程的匿名互斥对象

	hMutex = CreateMutex(NULL, TRUE, NULL) ;	// 创建一个属于当前线程的匿名互斥对象
	WaitForSingleObject(hMutex, INFINITE) ;
	ReleaseMutex(hMutex) ;				// 释放当前线程对互斥对象的所有权

	Sleep(4000) ;

	return 0 ; 
}

DWORD WINAPI ThreadProc1(LPVOID lpParameter) 
{	
	while (TRUE)
	{
		WaitForSingleObject(hMutex, INFINITE) ;
		if (tickets > 0)
		{
			Sleep(1) ;
			cout << "thread1 sell ticket: " << tickets-- << endl ;
		}
		else 
			break ;

		ReleaseMutex(hMutex) ;		// 释放hMutex互斥对象的所有权
	}

	return 0 ;
}

DWORD WINAPI ThreadProc2(LPVOID lpParameter) 
{
	while (TRUE)
	{
		WaitForSingleObject(hMutex, INFINITE) ;
		if (tickets > 0)
		{
			Sleep(1) ;
			cout << "thread2 sell ticket: " << tickets-- << endl ;
		}
		else 
			break ;

		ReleaseMutex(hMutex) ;
	}
	
	return 0 ;
}


和上面的两个程序(MultiThread1.cpp和MultiThread2.cpp)一样,先来看看运行结果,如下:
     

声明:由于我的电脑是多核的,造成第一种结果。对于单核电脑,是只可能出现第二种结果的。

来分析一下程序的执行过程,如下:

代码中的第28行中创建了一个互斥对象(hMutex),并且被当前线程(主线程main)所拥有(因为CreaeMutex()函数的第二个参数为TRUE),之后,程序向下执行到WaitForSingleObject()函数时, 操作系统会去判断拥有的互斥对象(hMutex)的线程(主线程main)的线程ID是否等于请求线程(此处,请求的线程就是主线程main)的线程ID,如果相等,那么即使此时互斥对象(hMutex)处于未通知状态(即非信号态),当前的请求线程(主线程main)也能获得此互斥对象(hMutex)的所有权,那么WaitForSingleObject()函数就会返回。【 对于一个线程多次拥有一个互斥对象是通过互斥对象内部的计数器来记录的】当我们第一次创建互斥对象是(hMutex),主线程拥有了这个互斥对象(hMutex),操作系统除了将该互斥对象(hMutex)的线程ID设置为主线程的线程ID,同时将该互斥对象(hMutex)的计数器变为1,当用WaitForSingleObject()函数去请求该互斥对象(hMutex)时,而此时的互斥对象(hMutex)是处于未通知状态(即非信号态)的,但是此时请求互斥对象(hMutex)的线程ID等于该互斥对象(hMutex)中的线程ID,所以我们仍能够请求到该互斥对象,那么操作系统通过互斥对象中的计数器来记录我们请求了多少次互斥对象,于是,此时该互斥对象(hMutex)在加1变为2,当我们在调用ReleaseMutex()函数时,实际上是将互斥对象内部的计数器进行减1操作,当程序中的互斥对象(hMutex)中的计数器减1操作后,其计数器的值还剩1,这样主线程还是没有失去对该互斥对象(hMutex)的的所有权,那么,该互斥对象(hMutex)还是处于未通知状态,所以,线程一和线程二就不能请求到对该互斥对象(hMetex)的所有权, 故线程一和线程二中的代码就不会被执行。


如果想要线程一和线程二中的代码被执行,我们可以在main()函数中再次调用ReleaseMutex()函数,将互斥对象(hMetex)的的计数器递减为0。

我测试程序是可以正常的执行的,这也和我们根据分析得到的结果一致,是不是很高兴啊 !!!  具体的代码就不贴出来了,程序运行的结果和MultThread1.cpp运行的结果一样。有兴趣的话,大家可以自己试一试 !!!


  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
VS2010之MFC入门到精通教程的pdf,共306页 ,10大章节,55小节。完整有图版 第一部分:VS2010/MFC 开发环境 VS2010/MFC 编程入门之前言 VS2010/MFC 编程入门之一(VS2010 与MSDN 安装过程图解) 第二部分:VS2010/MFC 应用程序框架 VS2010/MFC 编程入门之二(利用MFC 向导生成单文档应用程序框架) VS2010/MFC 编程入门之三(VS2010 应用程序工程中文件的组成结构) VS2010/MFC 编程入门之四(MFC 应用程序框架分析) VS2010/MFC 编程入门之五(MFC 消息映射机制概述) 第三部分:对话框 VS2010/MFC 编程入门之六(对话框:创建对话框模板和修改对话框属性) VS2010/MFC 编程入门之七(对话框:为对话框添加控件) VS2010/MFC 编程入门之八(对话框:创建对话框类和添加控件变量) VS2010/MFC 编程入门之九(对话框:为控件添加消息处理函数) VS2010/MFC 编程入门之十(对话框:设置对话框控件的Tab 顺序) VS2010/MFC 编程入门之十一(对话框:模态对话框及其弹出过程) VS2010/MFC 编程入门之十二(对话框:非模态对话框的创建及显示) VS2010/MFC 编程入门之十三(对话框:属性页对话框及相关类的介绍) VS2010/MFC 编程入门之十四(对话框:向导对话框的创建及显示) VS2010/MFC 编程入门之十五(对话框:一般属性页对话框的创建及显示) VS2010/MFC 编程入门之十六(对话框:消息对话框) VS2010/MFC 编程入门之十七(对话框:文件对话框) VS2010/MFC 编程入门之十八(对话框:字体对话框) VS2010/MFC 编程入门之十九(对话框:颜色对话框) 第四部分:常用控件 VS2010/MFC 编程入门之二十(常用控件:静态文本框) VS2010/MFC 编程入门之二十一(常用控件:编辑框Edit Control) VS2010/MFC 编程入门之二十二(常用控件:按钮控件Button、Radio Button 和Check Box) VS2010/MFC 编程入门之二十三(常用控件:按钮控件的编程实例) VS2010/MFC 编程入门之二十四(常用控件:列表框控件ListBox) VS2010/MFC 编程入门之二十五(常用控件:组合框控件Combo Box) VS2010/MFC 编程入门之二十六(常用控件:滚动条控件Scroll Bar) VS2010/MFC 编程入门之二十七(常用控件:图片控件Picture Control) VS2010/MFC 编程入门之二十八(常用控件:列表视图控件List Control 上) VS2010/MFC 编程入门之二十九(常用控件:列表视图控件List Control 下) VS2010/MFC 编程入门之三十(常用控件:树形控件Tree Control 上) VS2010/MFC 编程入门之三十一(常用控件:树形控件Tree Control 下) VS2010/MFC 编程入门之三十二(常用控件:标签控件Tab Control 上) VS2010/MFC 编程入门之三十三(常用控件:标签控件Tab Control 下) 第五部分:菜单、工具栏与状态栏 VS2010/MFC 编程入门之三十四(菜单:VS2010 菜单资源详解) VS2010/MFC 编程入门之三十五(菜单:菜单及CMenu 类的使用) VS2010/MFC 编程入门之三十六(工具栏:工具栏资源及CToolBar 类) VS2010/MFC 编程入门之三十七(工具栏:工具栏的创建、停靠与使用) VS2010/MFC 编程入门之三十八(状态栏的使用详解) 第六部分:文档、视图和框架 VS2010/MFC 编程入门之三十九(文档、视图和框架:概述) VS2010/MFC 编程入门之四十(文档、视图和框架:各对象之间的关系) VS2010/MFC 编程入门之四十一(文档、视图和框架:分割窗口) 第七部分:MFC 常用类 VS2010/MFC 编程入门之四十二(MFC 常用类:CString 类) VS2010/MFC 编程入门之四十三(MFC 常用类:CTime 类和CTimeSpan 类) VS2010/MFC 编程入门之四十四(MFC 常用类:定时器Timer) VS2010/MFC 编程入门之四十五(MFC 常用类:CFile 文件操作类) VS2010/MFC 编程入门之四十六(MFC 常用类:MFC 异常处理) 第八部分:字体和文本输出 VS2010/MFC 编程入门之四十七(字体和文本输出:CFont 字体类) VS2010/MFC 编程入门之四十八(字体和文本输出:文本输出) 第九部分:图形图像 VS2010/MFC 编程入门之四十九(图形图像:CDC 类及其屏幕绘图函数) VS2010/MFC 编程入门之五十(图形图像:GDI 对象之画笔CPen) VS2010/MFC 编程入门之五十一(图形图像:GDI 对象之画刷CBrush) 第十部分:Ribbon 界面开发 VS2010/MFC 编程入门之五十二(Ribbon 界面开发:创建Ribbon 样式的应用程序框架) VS2010/MFC 编程入门之五十三(Ribbon 界面开发:为Ribbon Bar 添加控件) VS2010/MFC 编程入门之五十四(Ribbon 界面开发:使用更多控件并为控件添加消息处理函数)

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

aworkholic

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

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

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

打赏作者

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

抵扣说明:

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

余额充值