友情提示:如果本网页打开太慢或显示不完整,请尝试鼠标右键“刷新”本网页!
C语言实例教程(PDF格式)-第73部分
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部! 如果本书没有阅读完,想下次继续接着阅读,可使用上方 "收藏到我的浏览器" 功能 和 "加入书签" 功能!
在OnStartthread()函数中添加如下代码:
void CThreadView::OnStartthread()
{
// TODO: Add your mand handler code here
HWND hWnd = GetSafeHwnd();
AfxBeginThread(ThreadProc; hWnd; THREAD_PRIORITY_NORMAL);
}
添加的代码将调用ThreadProc(),这个函数是新添加的线程的控制函
数,所以还需要在程序中添加这个函数。
在ThreadView。cpp中OnStartthread()的上面添加函数ThreadProc
()。
l 注意:
…………………………………………………………Page 641……………………………………………………………
l 这个函数是一个全局函数,而并非是CThreadView类的成员函数,
尽管它在CThreadView类的执行文件中。
在函数ThreadProc()中添加如下代码:
UINT ThreadProc(LPVOID param)
{
::MessageBox((HWND)param; 〃Thread activated。〃; 〃Thread〃; MB_OK);
return 0;
}
这个线程实际上并没有作什么,它仅仅报告它被启动了。
在函数前面的两个冒号表明是在调用全局函数,对于Windows程序员
来说,这通常称为API或SDK调用。
当你运行这个程序后,主窗口出现。选择 “线程”菜单中的 “启动线
程”菜单选项,系统启动一个线程,并且显示一个消息框,如图12。1
所示。
图12。 1 线程启动消息框
第二节 线程间通信
通常,一个次要的线程为主线程执行一定的任务,这也暗示这在主线
程和次要线程之间需要有一个联系的渠道。有几种方法可以完成这些
联系任务:使用全局变量、使用CEven类或者使用消息。本节将介绍
这几种方法。
(1) 使用全局变量通信
假定你需要你的程序能够停止线程。你需要一个告诉线程何时停止的
方法。一种方法是建立一个全局变量,然后让线程监督这个全局变量
是否为标志线程终止的值。为了实现这种方法,按照如下步骤修改前
面创建的Thread程序。
1。 在 “线程”菜单中添加菜单项 “停止线程”,ID为
…………………………………………………………Page 642……………………………………………………………
ID_STOPTHREAD。
2。 为 “停止线程”添加消息处理函数OnStopthread()。
3。 在ThreadView。cpp文件中添加一个全局变量threadController。
添加方法是在ThreadView。cpp的最上面,在endif下面添加下面的语
句:
volatile int threadController;
关键字volatile表示你希望这个变量可以被外面使用它的线程修改。
4。 修改OnStartthread()函数,代码如下所示:
void CThreadView::OnStartthread()
{
// TODO: Add your mand handler code here
threadController = 1;
HWND hWnd = GetSafeHwnd();
AfxBeginThread(ThreadProc; hWnd; THREAD_PRIORITY_NORMAL);
}
现在你可能已经猜到threadController的值决定线程是否继续。
5。 在OnStopthread()函数中添加下列代码:
threadController = 0;
6。 修改ThreadProc()函数的代码,代码如下:
UINT ThreadProc(LPVOID param)
{
::MessageBox((HWND)param; 〃Thread activated。〃; 〃Thread〃; MB_OK);
while (threadController == 1)
{
;
}
…………………………………………………………Page 643……………………………………………………………
::MessageBox((HWND)param; 〃Thread stopped。〃; 〃Thread〃; MB_OK);
return 0;
}
现在线程首先显示一个消息框,告诉用户线程被启动了。然后通过一
个while循环检查全局变量threadController,等待它的值变成0。尽
管这个while循环微不足道,但是你在这里可以加上执行你希望的任
务的代码。
现在编译并运行这个程序,选择 “线程”菜单中的 “启动线程”菜单
项启动一个线程,这是弹出如图12。1所示的对话框。然后选择 “线
程”主菜单中的 “停止菜单”菜单项,这时弹出如图12。2所示的对话
框,告诉用户线程已经终止。
图12。 2 线程关闭消息框
(2) 使用用户自定义消息通信
现在你有了一个简单的用于从主线程中联系附加线程的方法。反过
来,如何从附加线程联系主线程呢?最简单的实现这种联系的方法是
在程序中加入用户定义的Windows消息。
首先,要定义用户消息。这一步很容易,例如:
const WM_USERMSG = WM_USER + 100;
WM_USER变量是由Windows定义的,它是第一个有效的用户消息数。因
为你的程序的其它部分也会使用用户消息,故将新的用户消息
WM_USERMSG设置为WM_USER+100 。
在定义了用户消息之后,你应当在线程中调用::PostMessage()函数
来向主线程发送你所需要的消息。一般按照下面的方式调
用::PostMessage()函数:
::PostMessage((HWND)param; WM_USERMSG; 0; 0);
PostMessage()的四个参数分别是接收消息的窗口的句柄、消息的
ID、消息的WPARAM和LPARAM参数。
…………………………………………………………Page 644……………………………………………………………
将下面的语句加入到ThreadView。h中CThreadView类声明的上面。
const WM_THREADENDED = WM_USER + 100;
仍然是在此头文件中,在消息映射中加入下列语句,注意要加到
AFX_MSG的后面和DECLARE_MESSAGE_MAP的前面。
afx_msg LONG OnThreadended();
然后切回到ThreadView。cpp,在类的消息映射中加入下列语句,位置
在}}AFX_MSG_MAP之后。
ON_MESSAGE(WM_THREADENDED; OnThreadended)
再用下面的语句更改ThreadProc()函数。
UINT ThreadProc(LPVOID param)
{
::MessageBox((HWND)param; 〃Thread activated。〃; 〃Thread〃; MB_OK);
while (threadController == 1)
{
;
}
::PostMessage((HWND)param; WM_THREADENDED; 0; 0);
return 0;
}
在CThreadView中添加下面的成员函数。
LONG CThreadView::OnThreadended(WPARAM wParam; LPARAM lParam)
{
AfxMessageBox(〃Thread ended。〃);
return 0;
}
…………………………………………………………Page 645……………………………………………………………
图12。 3 线程终止对话框
当你重新运行这个程序时,选择 “线程”主菜单中的 “启动线程”菜
单选项,弹出一个消息框告诉你线程已经启动。为了结束这个线程,
选择 “线程”主菜单中的 “停止菜单”菜单选项,这将弹出一个如图
12。3所示的消息框告诉你线程已经停止。
(3) 使用Event对象通信
一个比较复杂的在两个线程间通信的方法是使用Event对象,在MFC下
也就是CEvent类对象。一个Event对象可以有两种状态:通信状态和
非通信状态。线程监视着Event对象的状态,并由此在合适的时间执
行它们的操作。
创建一个CEvent类的对象很简单,如下:
CEvent threadStart;
实际上,CEvent的构造函数形式如下:
CEvent( BOOL bInitiallyOwn = FALSE; BOOL bManualReset = FALSE;
LPCTSTR lpszName = NULL; LPSECURITY_ATTRIBUTES lpsaAttribute = NULL );
4个参数含义如下:
bInitiallyOwn 布尔量。如果值是True,用于CMultilock和
CSingleLock对象的线程将被允许。如果值为False,所有希望访问资
源的线程必须等待。缺省值为False。
bManualReset 布尔量。如果值为True,则Event对象是手动对象。如
果值为False,则Event对象是自动对象。缺省值为True。
lpszName CEvent对象的名称。如果事件对象被多个进程使用时必须
提供一个名称。缺省值为NULL。
lpsaAttribute CEvent对象的安全属性,与在Win32中的
SECURITY_ATTRIBUTES 相同。
尽管CEvent的构造函数有4个参数,但是经常不加任何参数的创建缺
…………………………………………………………Page 646……………………………………………………………
省的对象。当CEvent对象被创建之后,它 自动的处在未通信状态。为
了使其处在通信状态,可以调用其成员函数SetEvent ,如下所
示:
threadStart。SetEvent();
在执行完上述语句之后,threadStart将处在其通信状态。你的线程
应当监视它,这样才能知道何时执行。线程是通过调用如下Windows
API函数WaitForSingleObject()来监视CEvent对象的,形式如下:
::WaitForSingleObject(threadStart。m_hObject; INFINITE);
预定义的常量INFINITE告诉WaitForSingleObject()直到指定的
CEvent对象处在通信状态时才返回。换句话说,如果你把
WaitForSingleObject()放在线程的开头,系统将挂起线程直到
CEvent对象处在通信状态。当主线程准备好后,你应当调用SetEvent
()函数。
一旦线程不再挂起,它就可以运行了。但是,如果此时你还想和线程
通信,线程必须监视下一个CEvent对象处在通信状态,故你需要再次
调用WaitForSingleObject()函数,此时需要将等待时间设置为0,如
下所示:
::WaitForSingleObject(threadend。m_hObject; 0);
在这种情况下,如果WaitForSingleObject()返回值为
WAIT_OBJECT_0;则CEvent对象处在通信状态。否则,CEvent对象处在
非通信状态。
下面的例子说明如何使用CEvent类在两个线程间通信。按照以下步骤
进行:
1。 在ThreadView。cpp 中#include 〃ThreadView。h〃语句后面加上
#include 〃afxmt。h〃。
2。 在ThreadView。cpp 中volatile int threadController语句后加上
下列语句:
CEvent threadStart;
CEvent threadEnd;
删除语句volatile int threadController。
…………………………………………………………Page 647……………………………………………………………
3。 用下面的代码更换ThreadProc()函数。
UINT ThreadProc(LPVOID param)
{
::WaitForSingleObject(threadStart。m_hObject; INFINITE);
::MessageBox((HWND)param; 〃Thread activated。〃; 〃Thread〃; MB_OK);
BOOL keepRunning = TRUE;
while (keepRunning)
{
int result = ::WaitForSingleObject(threadEnd。m_hObject; 0);
if (result == WAIT_OBJECT_0)
keepRunning = FALSE;
}
::PostMessage((HWND)param; WM_THREADENDED; 0; 0);
return 0;
}
4。 用下面的语句替换OnStartthread()函数中的内容。
threadStart。SetEvent();
5。 用下面的语句替换OnStopthread()函数中的内容。
threadEnd。SetEvent();
6。 使用ClassWizard为CthreadView处理WM_CREATE消息的函数
OnCreate(),并在TODO后面添加代码。OnCreate()函数如下所示:
int CThreadView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CView::OnCreate(lpCreateStruct) == …1)
return …1;
// TODO: Add your specialized creation code here
…………………………………………………………Page 648……………………………………………………………
HWND hWnd = GetSafeHwnd();
AfxBeginThread(ThreadProc; hWnd);
return 0;
}
编译并运行这个程序,新版本的程序运行起来和旧版本的程序一样,
但是,新版本的程序为了实现在主线程和次要线程间通信,既使用了
CEvent类,又使用了用户定义的Windows消息。
新版本的程序和旧版本的程序的一个大的不同在于次要线程在
OnCreate()函数中被启动。然而由于线程函数的第一行即调用
WaitForSingleObject(),所以此线程立即被挂起并且等待
threadStart处于通信状态。
当threadStart处在通信状态时,新线程显示消息框,然后进入while
循环。这个while循环继续执行直到threadEnd处在通信状态,然后线
程向主线程发送一个WM_THREADENDED消息并退出。因为此线程是在
OnCreate()函数中被创建的;一旦结束,不会被重新启动。
第三节 线程同步
使用多线程可以带来一些非常有趣的问题。例如,如何防止两个线程
在同一时间访问同一数据?例如,假设一个线程正在更新一个数据
集,而同时另外一个线程正在读取数据集,结果如何?第二个线程将
会读取到错误的数据,因为数据集中只有一部分元素被更新过。
保持在同一个进程内的线程工作协调一致称之为线程同步。Event对
象实际上就是线程同步的一种形式。在本节中,你将会学到三种使你
的多线程程序更安全的线程同步对象—critical section、互斥对象
(mutex)、信号量 (semaphore)。
(1) 使用Critical Section
Critical Section是一种保证在一个时间只有一个线程访问数据集的
非常简单的方法。当你使用Critical Section,你给了线程一个它们
必须共享的对象。任何拥有Critical Section对象的线程可以访问被
保护起来的数据。其它线程必须等待直到第一个线程释放了Critical
Section对象,此后其它线程可以按照顺序抢 占Critical Section对
…………………………………………………………Page 649……………………………………………………………
象,访问数
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!