c+ 多线程编程,c++ 多线程编程
写一个耗时的单线程程序:
新建一个基于对话框的应用SingleThread,在主对话框IDD_SINGLETHREAD_DIALOG中添加一个按钮,ID为IDC_SLEEP_SIX_SECOND,标题为“延迟6秒”。添加按钮的响应函数,代码如下:
voidCSingleThreadDlg:OnSleepSixSecond()
{
睡眠(6000);//延迟6秒
}
编译运行应用程序,点击“延迟6秒”按钮,你会发现在这6秒内,应用程序就像“崩溃”一样,不响应其他消息。为了更好的处理这种耗时的操作,我们有必要学习——多线程编程。
二。多线程概述
进程和线程是操作系统的概念。进程是应用程序的执行实例。每个进程由私有虚拟地址空间、代码、数据和其他系统资源组成。在进程期间创建的资源随着进程的终止而被销毁,当进程终止时,所使用的系统资源被释放或关闭。
线程是进程中的一个执行单元。在系统创建一个进程后,它实际上启动了执行该进程的主执行线程。主执行线程以函数地址的形式向Windows系统提供程序的起始点,如main或WinMain函数。执行的主线程终止,进程也终止。
每个进程至少有一个执行的主线程,它是由系统自动创建的,不需要用户的主动参与。用户根据需要在应用中创建其他线程,多个线程在同一个进程中并发运行。一个进程中的所有线程都在进程的虚拟地址空间中,共享这些虚拟地址空间、全局变量和系统资源,所以线程之间的通信非常方便,多线程技术得到了广泛的应用。
多线程可以实现并行处理,避免了某个任务长时间占用CPU时间。需要注意的是,目前大部分电脑都是单处理器(CPU)。为了运行所有这些线程,操作系统会给每个独立的线程安排一些CPU时间,操作系统以轮换的方式给线程提供时间片,给人一种好像所有这些线程都在同时运行的错觉。因此,如果两个非常活跃的线程试图抢夺CPU的控制权,那么它们在切换线程时会消耗大量的CPU资源,从而降低系统的性能。在多线程编程中应该注意这一点。
Win32SDK函数支持多线程编程,提供操作系统原理中的同步、互斥、临界区等多种操作。在Visual 6.0中,还利用MFC类库实现了多线程编程,使得多线程编程更加方便。
第三,Win32API支持多线程编程
Win32提供了一系列API函数来创建、挂起、恢复、终止和通信线程。下面将选择一些重要的功能进行解释。
1、HANDLECreateThread(LP security _ ATTRIBUTESlpThreadAttributes,
DWORDdwStackSize,
LP thread _ START _ ROUTINElpStartAddress,
LPVOIDlpParameter,
DWORDdwCreationFlags,
LPDWORDlpThreadId);
该函数在其调用进程的进程空间中创建新线程,并返回已建立线程的句柄,其中参数描述如下:
LpThreadAttributes:指向SECURITY_ATTRIBUTES结构的指针,该结构决定线程的安全属性,一般设置为NULL;
DwStackSize:指定线程的堆栈深度,一般设置为0;
LpStartAddress:表示新线程开始执行时代码所在函数的地址,即线程的起始地址。一般是(LP thread _ start _ routine)thread func,这是线程函数名;
LpParameter:指定线程执行时传递给线程的32位参数,即线程函数的参数;
DwCreationFlags:由控制进程创建的附加标志,它可以有两个值。如果该参数为0,线程将在创建后立即开始执行;如果该参数为CREATE_SUSPENDED,则系统生成线程后,线程将被挂起,不会立即执行,直到调用函数ResumeThread
LpThreadId:该参数返回所创建线程的id;
如果创建成功,则返回线程的句柄;否则,返回NULL。
2、DWORDSuspendThread(HANDLEhThread);
这个函数用于暂停指定的线程。如果函数执行成功,线程的执行将被终止。3、DWORDResumeThread(HANDLEhThread);
这个函数用来结束线程的挂起状态并执行线程。4、vodiexitthread(DWORDdwExitCode);
这个函数用于线程终止自己的执行,主要在线程的执行函数中调用。参数dwExitCode用于设置线程的退出代码。5、BOOLTerminateThread(HANDLEhThread,DWORDdwExitCode);
一般来说,线程运行后,线程函数正常返回,但应用程序可以调用TerminateThread来强制终止线程的执行。每个参数的含义如下:
HThread:要终止的线程的句柄;
DwExitCode:用于指定线程的退出代码。
用TerminateThread()终止线程的执行是不安全的,可能会导致系统不稳定;虽然这个函数会立即终止线程的执行,但它不会释放线程占用的资源。所以一般不建议使用该功能。
6、BOOLPostThreadMessage(DWORDidThread,
UINTMsg,
WPARAMwParam,
LPARAMlParam);
此函数将消息放入指定线程的消息队列中,并在不等待线程处理消息的情况下返回消息。
IdThread:将接收消息的线程的ID;
Msg:指定要发送的消息;
WParam:与消息相关的word参数;
LParam:与消息相关的长参数;
调用该函数时,如果即将接收消息的线程没有创建消息循环,则该函数无法执行。
四。Win32API多线程编程例程
例程1多线程1
建立一个基于对话框的项目MultiThread1,在对话框IDD_MULTITHREAD1_DIALOG中添加两个按钮和一个编辑框。两个按钮的id分别是IDC_START和IDC_STOP,标题分别是START和STOP。IDC_STOP的属性被选择为禁用;编辑框的ID为IDC_TIME,属性为只读;
在MultiThread1Dlg.h文件中添加线程函数声明:voidThreadFunc();
注意,thread函数的声明应该在CMultiThread1Dlg类之外。在类CMultiThread1Dlg内添加一个受保护的变量:HANDLEhThread
DWORDThreadID
分别表示线程的句柄和ID。
在MultiThread1Dlg.cpp文件中添加全局变量m _ brun:volatileboolm _ brun;
M_bRun表示线程是否正在运行。
您应该注意到全局变量m_bRun使用了volatile修饰符。volatile修饰符的作用是告诉编译器不需要对变量进行优化,也就是不需要放在寄存器中,可以在外部改变值。Volatile是多线程引用的全局变量的一个非常重要的修饰符。
编写线程函数:voidThreadFunc()
{
CTimetime
CStringstrTime
m _ bRun=TRUE
while(m_bRun)
{
time=CTime:GetCurrentTime();
strTime=时间。格式(“% H:% M:% S”);
* SetDlgItemText(AfxGetMainWnd()-m _ hWnd,IDC_TIME,strTime);
睡眠(1000);
}
}
这个线程函数没有参数,也不返回函数值。只要m_bRun为真,线程就一直运行。
双击IDC_START按钮,完成按钮的消息功能:voidCMultiThread1Dlg:OnStart()
{
//TODO:addyourcontrolnotificationhandlercode here
hThread=CreateThread(NULL,
0,
(LP thread _ START _ ROUTINE)thread func,
空,
0,
ThreadID);
GetDlgItem(IDC _ START)-enable window(FALSE);
GetDlgItem(IDC _ STOP)-enable window(TRUE);
}
双击IDC_STOP按钮,完成按钮的消息功能:voidCMultiThread1Dlg:OnStop()
{
//TODO:addyourcontrolnotificationhandlercode here
m _ bRun=FALSE
GetDlgItem(IDC _ START)-enable window(TRUE);
GetDlgItem(IDC _ STOP)-enable window(FALSE);
}
编译运行这个例程,体验Win32API写的多线程。
例程2多线程2
这个线程演示了如何向线程发送一个整数参数,以及如何等待线程完成处理。
建立一个基于对话框的工程多线程2,在对话框IDD_MULTITHREAD2_DIALOG中添加一个编辑框和一个按钮,id分别为IDC_COUNT和IDC_START,按钮控件的标题为“START”;
在MultiThread2Dlg.h文件中添加线程函数声明:voidThreadFunc(intinteger);
注意,thread函数的声明应该在CMultiThread2Dlg类之外。
在类CMultiThread2Dlg内添加一个受保护的变量:HANDLEhThread
双字线程ID:
分别代表线程的句柄和身份号。
打开类向导,为编辑框IDC_COUNT添加内部的型变量m _ ncount .在多线程2Dlg.cpp文件中添加:voidthrefunc(亲密蒙古包)
{
英蒂
for(I=0);我整数;(一)
{
哔哔声(200.50);
睡眠(1000);
}
}
双击IDC_START按钮,完成该按钮的消息函数:无效c多线程2 DLG:论rt()
{
已更新(正确):
S7-1200可编程控制器:
hthread=createthread(空值,
0个,
(LP thread _ start _ routine)线程函数,
(请参阅*)整数,
0个,
线程ID:
getdlgitem(IDC _ start)-启用窗口(false);
WaitForSingleObject(hThread,infinite);
GetDlgItem(IDC_START)-启用窗口(正确):
}
顺便说一下等待单一对象-等待单一物件函数,其函数原型为:dword等待singleobject(handle,DWORDdwMilliseconds):
嘘嘘嘘为要监视的对象(一般为同步对象,也可以是线程)的句柄;
发展的宽度(Developed Width的缩写)毫秒数为嘘嘘嘘对象所设置的超时值,单位为毫秒;
当在某一线程中调用该函数时,线程暂时挂起,系统监视嘘嘘嘘所指向的对象的状态。如果在挂起的发展的宽度(Developed Width的缩写)毫秒数毫秒内,线程所等待的对象变为有信号状态,则该函数立即返回;如果超时时间已经到达发展的宽度(Developed Width的缩写)毫秒数毫秒,但嘘嘘嘘所指向的对象还没有变成有信号状态,函数照样返回。参数发展的宽度(Developed Width的缩写)毫秒数有两个具有特殊意义的值:0和无限的。若为0个,则该函数立即返回;若为无限的,则线程一直被挂起,直到嘘嘘嘘所指向的对象变为有信号状态时为止。
本例程调用该函数的作用是按下IDC_START按钮后,一直等到线程返回,再恢复IDC_START按钮正常状态。编译运行该例程并细心体会。
例程3多线程3
传送一个结构体给一个线程函数也是可能的,可以通过传送一个指向结构体的指针参数来完成。先定义一个结构体:
typedefstruct
{
intfirstArgu,
朗古杜古,
……
}myType 、* pmytype
创建线程时CreateThread(NULL、0、threadFunc、pMyType 、…);
在线程函数(线程函数)函数内部,可以使用""强制转换":
intint value=(((pmytype)LP void)-first argu;
long值=((pmytype)LP void)-秒dargu
…………
例程3多线程3将演示如何传送一个指向结构体的指针参数。
建立一个基于对话框的工程多线程3,在对话框idd _多线程3 _对话框中加入一个编辑框idc _毫秒,一个按钮IDC_START,标题为""开始",一个进度条IDC _进度1:
打开类向导,为编辑框idc _毫秒添加内部的型变量m _毫米波秒,为进度条IDC_PROGRESS1添加条控件型变量m _ ctrlprogress
在多线程3d DLG文件中添加一个结构的定义:structthreadInfo
{
uintnmillisecond(千分之一秒):
cprogressctrl * pctrlprogress
}:
线程函数的声明:uintthrefunc(lpvoidlpparam):
注意,二者应在类多线程3 DLG-多重执行绪3-多重执行绪3-多重执行绪3-多重执行绪3-多重执行绪3-多重执行绪3-多重执行绪3-多重执行绪3-多重执行绪3的外部。
在类多线程3 DLG-多重执行绪3-多重执行绪3-多重执行绪3-多重执行绪3-多重执行绪3-多重执行绪3-多重执行绪3-多重执行绪3-多重执行绪3内部添加受保护的型变量:句柄线程
双字线程ID:
分别代表线程的句柄和身份号。
在多线程3Dlg.cpp文件中进行如下操作:
定义公共变量threadinfoinfo线程信息;
双击按钮IDC_START,添加相应消息处理函数:无效c多线程3d DLG:在rt()上
{
//全部:在此处添加控制通知处理程序代码
已更新(正确):
信息。我的毫秒=m _我的毫秒;
信息。pctrl progress=m _ ctrl progress
hthread=createthread(空值,
0个,
(LP thread _ start _ routine)线程函数,
关于,
0个,
线程ID:
/*
getdlgitem(IDC _ start)-启用窗口(false);
WaitForSingleObject(hThread,infinite);
GetDlgItem(IDC_START)-启用窗口(正确):
*/
}
在函数boolc多线程3d DLG:在初始化对话框上()中添加语句:>
…………
//全部:addextrainitializationhere
m_ctrlProgress函数set range(0.99);
m _毫米波秒=10;
已更新(错误):
返回TRUE://返回真实的除非syusetthefnostcontrol
}
添加线程处理函数:uintthrefunc(lpvoidlppa)}
thread info * pinfo=(thread info *)lpparam;
for(inti=0);(一)
{
intmp=pinfo-毫米波;
pinfo-pct进展-setpos(I);
睡眠(ntemp);
}
返回0;
}
对了,如果在voidCMultiThread3Dlg:OnStart()函数中加入/* */语句,会发现编译运行后,进度条不刷新,主线程停止响应。原因是什么?这是因为WaitForSingleObject函数等待子线程(ThreadFunc)结束,导致线程死锁。因为WaitForSingleObject函数会挂起主线程(没有消息可以处理),而子线程ThreadFunc正在设置进度条,在检测到通知事件之前一直在等待主线程处理并返回刷新消息。这样两个线程都在互相等待,发生了死锁,编程时应该避免。
例程4多线程4
这个例程测试在Windows下可以创建的最大线程数。
建立一个基于对话框的项目MultiThread4,在对话框IDD_MULTITHREAD4_DIALOG中添加一个按钮IDC_TEST和一个编辑框IDC_COUNT,按钮标题为“TEST”,编辑框属性选择为只读;
在MultiThread4Dlg.cpp文件中,执行以下操作:
添加公共变量volatileBOOLm _ bRunFlag=TRUE
此变量指示线程创建是否可以继续。
添加线程功能:
DWORDWINAPIthreadFunc(LPVOIDthreadNum)
{
while(m_bRunFlag)
{
睡眠(3000);
}
return0
}
只要m_bRunFlag变量为真,线程就一直运行。
双击按钮IDC_TEST添加其响应消息函数:voidCMultiThread4Dlg:OnTest()
{
DWORDthreadID
GetDlgItem(IDC _ TEST)-enable window(FALSE);
longn count=0;
while(m_bRunFlag)
{
if(CreateThread(NULL,0,threadFunc,NULL,0,threadID)==NULL)
{
m _ bRunFlag=FALSE
打破;
}
其他
{
nCount
}
}
//继续创建线程,直到不能再创建为止
m _ nCount=nCount
update data(FALSE);
睡眠(5000);
//延迟5秒,等待所有创建的线程完成。
GetDlgItem(IDC _ TEST)-enable window(TRUE);
m _ bRunFlag=TRUE
}
5.MFC对多线程编程的支持
MFC中有两种线程,称为工作线程和用户界面线程。两者的主要区别在于工作线程没有消息循环,而用户界面线程有自己的消息队列和消息循环。
Workers没有消息机制,通常用于执行后台计算和维护任务,如冗长的计算过程、打印机的后台打印等。用户界面线程通常用于独立于其他线程处理用户输入,并响应用户和系统生成的事件和消息。但是,对于Win32 API编程,这两种线程没有区别。两者都只需要线程的起始地址就可以启动线程执行任务。
在MFC中,全局函数AfxBeginThread()通常用于创建和初始化线程的运行。该函数有两种重载形式,分别用于创建工作线程和用户界面线程。两种重载函数的原型和参数描述如下:
(1)CWinThread * AfxBeginThread(AFX _ threadprocppfnthreadproc,
LPVOIDpParam,
n PRIORITY=THREAD _ PRIORITY _ NORMAL
UINTnStackSize=0,
DWORDdwCreateFlags=0,
LP security _ ATTRIBUTESlpSecurityAttrs=NULL);
PfnThreadProc:指向工作线程执行函数的指针。线程函数的原型必须声明如下:uintexecuting function(lpvoidparam);
请注意,ExecutingFunction()应该返回一个UINT类型的值,以指示函数结束的原因。通常,返回0表示执行成功。
PParam:传递给线程函数的32位参数,执行函数将以某种方式解释该值。可以是一个数值,也可以是一个结构的指针,甚至可以忽略;
NPriority:线程的优先级。如果为0,则该线程与其父线程具有相同的优先级;
NSTXSIZE:线程为自己分配nStackSize,单位是字节。如果将nStackSize设置为0,则将线程的堆栈设置为与父线程的堆栈相同的大小;
DwCreateFlags:如果为0,线程将在创建后立即开始执行。如果是CREATE_SUSPEND,线程创建后会立即挂起;
LpSecurityAttrs:线程的安全属性指针,通常为NULL;
(2)CWinThread * AfxBeginThread(CRuntimeClass * pThreadClass,
intn PRIORITY=THREAD _ PRIORITY _ NORMAL,
UINTnStackSize=0,
DWORDdwCreateFlags=0,
LP security _ ATTRIBUTESlpSecurityAttrs=NULL);
PThreadClass是指向CWinThread的导出类的运行时类对象的指针,它定义了开始、退出等。创建的用户界面线程的;其他参数的含义与表1中的含义相同。这个函数原型生成的线程也有一个消息机制,在以后的例子中我们会发现和主线程几乎是一样的。
下面简单解释一下CWinThread类的数据成员和常用函数。
M_hThread:当前线程的句柄;
M_nThreadID:当前线程的id;
M_pMainWnd:指向应用程序主窗口的指针
BOOLCWinThread:CreateThread(DWORDdwCreateFlags=0,
UINTnStackSize=0,
LP security _ ATTRIBUTESlpSecurityAttrs=NULL);
该函数中的dwCreateFlags、nStackSize、lpSecurityAttrs参数与API函数CreateThread中的相应参数具有相同的含义。该函数成功执行并返回非零值,否则返回0。
一般调用AfxBeginThread()来一次性创建并启动线程,但也可以通过两步的方法来创建线程:首先创建CWinThread类的对象,然后调用其成员函数CreateThread()来启动线程。
virtualBOOLCWinThread:init instance();
重载此函数以控制用户界面线程实例的初始化。如果初始化成功,将返回一个非零值;否则,它将返回0。用户界面线程经常重载这个函数,工作线程一般不使用InitInstance()。virtualintCWinThread:exit instance();
在线程结束之前重载这个函数来做一些必要的清理工作。该函数返回线程的退出代码,0表示执行成功,非零值用于标识各种错误。像InitInstance()成员函数一样,这个函数只适用于用户界面线程。
六、MFC多线程编程实例
在VisualC 6.0编程环境中,我们可以用C风格编写32位Win32应用程序,也可以用MFC类库编写C风格的应用程序,两者各有优缺点。基于Win32的应用程序执行代码少,运行效率高,但要求程序员编写更多的代码,需要管理系统提供给程序的所有资源。基于MFC类库的应用程序可以快速设置应用程序。类库为程序员提供了大量的封装类,DeveloperStudio为程序员提供了一些管理用户源程序的工具。它的缺点是类库代码庞大。由于使用类库的优势,如快速、简单、功能强大等,除非有特殊需要,否则VisualC建议使用MFC类库进行程序开发。
我们知道,MFC中有两种线程:用户界面线程和工作线程。我们将分别举例说明。
MFC类库编程实现工作线程
例程5多线程5
为了与Win32API进行比较,我们使用MFC类库编程实现了例程3MultiThread3。
建立一个基于对话框的项目MultiThread5,并添加一个编辑框IDC _毫秒,一个标题为“开始”的按钮IDC_START和一个进度条IDC _ PROGRESS1到对话框IDD _ MULTITHREAD5 _ DIALOG
打开ClassWizard,在编辑框IDC _毫秒中添加int变量m_nMilliSecond,在进度条IDC_PROGRESS1中添加CProgressCtrl变量m _ ctrl progress
在MultiThread5Dlg.h文件中添加结构的定义:structthreadInfo。
{
UINTnMilliSecond
CProgressCtrl * pctrlrprogress;
};
函数声明:UINTThreadFunc(LPVOIDlpParam);
注意这两个应该在CMultiThread5Dlg类之外。
在CMultiThread5Dlg类中添加一个受保护的变量:
CWinThread * pThread
在MultiThread5Dlg.cpp文件中,执行以下操作:定义公共变量:threadInfoInfo
双击IDC_START添加相应的报文处理功能:
voidCMultiThread5Dlg:OnStart()
{
//TODO:addyourcontrolnotificationhandlercode here
update data(TRUE);
info . nMilliSecond=m _ nMilliSecond;
info . pctrlprogress=m _ ctrl progress;
pThread=AfxBeginThread(thread func,
info);
}
在函数boolcmultithread 3d LG:oninitdialog()中添加一条语句:{
……
//TODO:addextinitializationhere
m_ctrlProgress。SetRange(0,99);
m _ nMilliSecond=10
update data(FALSE);
returnTRUE//returntruunellessyousetthefocustoacontrol
}
添加线程处理程序:UINTThreadFunc(LPVOIDlpParam)
{
threadInfo * pInfo=(threadInfo *)lpParam;
for(inti=0);(一)
{
intmp=pinfo-毫米波;
pinfo-pct进展-setpos(I);
睡眠(ntemp);
}
返回0;
}
用MFC(消歧义)类库编程实现用户界面线程
创建用户界面线程的步骤:
使用类向导创建类cwinthread的派生类(以小心线程类为例)类quethread:public cwinthread
{
DECLARE_DYNCREATE(线程)
受保护:
线程();//受保护构造被动态操作-保护建构函式
//属性
观众:
//操作-作业
观众:
//复盖
//classwizardgeneradvirtualfunction复盖
/{ { AFX _虚拟(线程)
观众:
virtualboolinitinstance
virtualessinstance();
/} } AFX _虚拟
//实施
受保护:
virtual ~ cuthread();
//generateddmessagemaftons
/{{AFX_MSG(线程)
//注意-此处的classwizardwillandremovememberfunction .
/}}AFX_MSG文件
声明消息映射()
}:
重载函数初始实例()和exitinstance().boolcuitthread:init实例()
{
cframe wnd * wnd=newcframe wnd
wnd- Create(NULL, UIThreadWindow ):
wnd-显示窗口(SW _ show);
wnd-更新窗口();
最不发达国家=wnd:
返回真实:
}
创建新的用户界面线程voidcuitthread DLG:on按钮1()
{
线程* pthread=newcuthread();
pThread- CreateThread():
}
请注意以下两点:
(一)在ui thread DLG。CPP(国际刑事警察组织)的开头加入语句:#包括UIThread.h
乙。_把UIThread.h。用户程序中类线程()的构造函数的特性由受保护的改为公众人物。
用户界面线程的执行次序与应用程序主线程相同,首先调用用户界面线程类的初始实例()函数,如果返回是真的,继续调用线程的运行()函数,该函数的作用是运行一个标准的消息循环,并且当收到WM_QUIT(退出)消息后中断,在消息循环过程中,运行()函数检测到线程空闲时(没有消息),也将调用奥尼德尔()函数,最后运行()函数返回,MFC调用exitinstance()函数清理资源。
你可以创建一个没有界面而有消息循环的线程,例如:你可以从cwinthread派生一个新类,在初始实例(初始实例)函数中完成某项任务并返回假的,这表示仅执行初始实例(初始实例)函数中的任务而不执行消息循环,你可以通过这种方法,完成一个工作者线程的功能。
例程6多线程6
建立一个基于对话框的工程多线程6,在对话框idd _多线程6 _对话框中加入一个按钮IDC_UI_THREAD,标题为""用户界面线程""
右击工程并选中“新阶级…”为工程添加基类为cwinthread派生线程类小心点儿。
给工程添加新对话框idd _ uithreaddlg,标题为""线程对话框""。
为对话框idd _ uithreaddlg创建一个基于cddialog(对话方块)的类熟线程Dlg .使用类向导为cuithreaddlg(线程控制)类添加wm _ lbuttondown(消歧义)消息的处理函数OnLButtonDown(消歧义),如下:voidcuithredlg:onbutton down(uintnflags,c point)
{
afxmmessagebox( youclicktheleftbutton!);
CD dialog:onbutton down(NFL标记,句号);
}
在UIThread.h。用户程序中添加#包括uithreaddlg。 h
并在小心线程类中添加受保护的变量DLG:类线程:公共线程
{
DECLARE_DYNCREATE(线程)
受保护:
线程();//受保护构造被动态操作-保护建构函式
//属性
观众:
//操作-作业
观众:
//复盖
//classwizardgeneradvirtualfunction复盖
/{ { AFX _虚拟(线程)
观众:
virtualboolinitinstance
virtualessinstance();
/} } AFX _虚拟
//实施
受保护:
cuithreaddlgm _ dlg:
virtual ~ cuthread();
//generateddmessagemaftons
/{{AFX_MSG(线程)
//注意-此处的classwizardwillandremovememberfunction .
/}}AFX_MSG文件
声明消息映射()
}:
分别重载初始实例()函数和exitinstance()函数:boolcuitthread:init实例()
{
m_dlg(消歧义)。创建(idd _ uithreaddlg):
m_dlg(消歧义)。ShowWindow(SW_SHOW):
m _ pman内部m _ dlg
返回真实:
}
int thread:existence()
{
m_dlg(消歧义)。destrowindow();
返回cwinthread:existence();
}
双击按钮IDC_UI_THREAD,添加消息响应函数:无效c多线程6 DLG
{
cwinthread * pthread=afxbegmentread(runtime _ class(cuthread));
}
并在多线程6Dlg.cpp的开头添加:#包括UIThread.h
好了,编译运行程序。每次点击“用户界面线程”按钮,都会弹出一个线程对话框。在任一线程对话框中按鼠标左键都会弹出一个消息框。
七。线程间的通信
一般来说,应用程序中的一个辅线程总是为主线程执行特定的任务,所以主线程和辅线程之间必须有一个信息传递的通道,也就是主线程和辅线程之间的通信。这种线程间的通信不仅不可避免,而且在多线程编程中也是复杂而频繁的,下面将对此进行说明。
使用全局变量进行通信
因为属于同一个进程的所有线程共享操作系统分配的资源,所以解决线程间通信最简单的方法就是使用全局变量。对于标准类型的全局变量,我们建议使用volatile修饰符,它告诉编译器不需要优化变量,也就是不需要放在寄存器中,值可以在外部更改。如果要在线程间传输的信息很复杂,我们可以定义一个结构,通过传递一个指向该结构的指针来传输信息。
使用自定义消息
我们可以在一个线程的执行函数中向另一个线程发送自定义消息,达到通信的目的。一个线程通过操作系统向另一个线程发送消息。利用Windows操作系统的消息驱动机制,当一个线程发出消息时,操作系统首先接收消息,然后将消息转发给目标线程。接收消息的线程必须已经建立了消息循环。
例程7多线程7
这个例程演示了如何使用自定义消息进行线程间通信。首先主线程发送消息WM_CALCULATE给CCALECTREAD线程,CCALECTREAD线程收到消息后进行计算,然后将WM_DISPLAY消息发送给主线程,主线程收到消息后显示计算结果。
建立一个基于对话框的项目MultiThread7,在对话框IDD_MULTITHREAD7_DIALOG中添加三个单选按钮IDC_RADIO1、IDC_RADIO2和IDC_RADIO3,标题为1 2 3 4.10, 1 2 3 4 .50和1 2 3 4.分别是100。添加标题为“SUM”的按钮IDC_SUM。添加标签框IDC_STATUS,选择“Border”作为属性;
MultiThread7Dlg.h中定义了以下变量:protected:
intnAddend
表示加数的大小。
双击三个单选按钮添加消息响应函数:voidCMultiThread7Dlg:OnRadio1()
{
nAddend=10
}
voidCMultiThread7Dlg:OnRadio2()
{
nAddend=50
}
voidCMultiThread7Dlg:OnRadio3()
{
nAddend=100
}
并在OnInitDialog函数中完成相应的初始化工作:boolcmultithread 7 DLG:OnInitDialog()
{
……
((CButton *)GetDlgItem(IDC _ radio 1))-set check(TRUE);
nAddend=10
……
Add:在MultiThread7Dlg.h中# include calculatethhread.h
#defineWM_DISPLAYWM_USER 2
class cmultithread 7 DLG:public dialog
{
//构造
公共:
cmultithread 7 DLG(CWnd * p parent=NULL);//标准构造函数
CCalculateThread * m _ pCalculateThread;
……
受保护:
intnAddend
LRESULTOnDisplay(WPARAMwParam,LPARAMlParam);
……
MultiThread7Dlg.cpp中的add:begin _ message _ map(cmultithread 7 DLG,cdialog)
……
打开消息(WM_DISPLAY,OnDisplay)
END_MESSAGE_MAP()
lresultcmultithread 7 DLG:on display(WPARAMwParam,LPARAMlParam)
{
int ntemp=(int)wParam;
SetDlgItemInt(IDC_STATUS,nTemp,FALSE);
return0
}
上面的代码使主线程类CMultiThread7Dlg能够处理WM_DISPLAY消息,即在IDC_STATUS tab框中显示计算结果。
双击按钮IDC_SUM添加消息响应函数:voidCMultiThread7Dlg:OnSum()
{
m_pCalculateThread=
(ccalculateththread *)AfxBeginThread(RUNTIME _ CLASS(ccalculateththread));
睡眠(500);
m _ pCalculateThread-posthreadmessage(WM _ CALCULATE,nAddend,NULL);
}
OnSum()的作用是设置一个CalculateThread,延时发送WM_CALCULATE消息给线程。
右键单击项目并选择“NewClass…”来添加线程类CCalculateThread,其基类是CWinThread。
在CalculateThread.h文件中添加#defineWM_CALCULATEWM_USER 1
classccaluthethread:publicwinthread
{
……
受保护:
afx _ msgLONGOnCalculate(UINTwParam,long param);
……
在CalculateThread.cpp文件中添加long calculate thread:oncalculate(uintparam,longlparam)
{
intnTmpt=0;
for(inti=0;I=(int)wParam;我)
{
nTmpt=nTmpt I;
}
睡眠(500);
* PostMessage((HWND)(GetMainWnd()-GetSafeHwnd()),WM_DISPLAY,nTmpt,NULL);
return0
}
BEGIN _ MESSAGE _ MAP(CCalculateThread,CWinThread)
//{ { AFX _ MSG _ MAP(CCalculateThread)
//注意-theClassWizardwilladdandremovemappingmacroshere。
//}}AFX_MSG_MAP
线程消息(WM计算,OnCalculate)
//与主线程进行比较,注意它们之间的区别
END_MESSAGE_MAP()
在CalculateThread.cpp文件的开头添加一行:#includeMultiThread7Dlg.h
上面的代码将WM_CALCULATE消息添加到CCalculateThread类中。消息的响应函数是OnCalculate,它的作用是根据参数wParam的值进行累加。累积的结果在临时变量nTmpt中,延迟0.5秒。WM_DISPLAY消息被发送到主线程进行显示,nTmpt作为参数传递。
编译并运行这个例程,实现如何在线程间传递消息。
八。线程同步
虽然多线程可以给我们带来好处,但是还有很多问题需要解决。例如,对于磁盘驱动器这样独占系统资源,由于一个线程可以执行一个进程的任意代码段,且线程的运行是由系统调度自动完成的,具有一定的不确定性,因此有可能两个线程同时操作磁盘驱动器,导致操作错误;例如,对于银行系统中的计算机,一个线程可用于更新其用户数据库,而另一个线程可用于读取数据库以响应存款人的需求。读取数据库的线程很可能会读取不完全更新的数据库,因为在读取过程中可能只有部分数据被更新。
让属于同一个进程的线程和谐工作,叫做线程同步。MFC提供了多种同步对象。下面,我们只介绍最常用的四种:
临界截面
事件(事件)
互斥体(CMutex)
信号量
通过这些类,我们可以很容易地实现线程同步。
一、使用CCriticalSection类
当多个线程访问一个独占的共享资源时,可以使用临界区对象。在任何时候,只有一个线程可以拥有临界区对象,拥有临界区的线程可以访问受保护的资源或代码段。其他希望进入临界区的线程将被挂起,直到具有临界区的线程放弃临界区,从而确保多个线程不会同时访问共享资源。
CCriticalSection类使用起来非常简单。步骤如下:
定义一个CCriticalSection类的全局对象(这样所有线程都可以访问),比如CCRITICALSECTIONCIAL _ SECTION
在访问需要保护的资源或代码之前,调用CCriticalSection类的成员Lock()获取临界区对象:critical_section。lock();
在线程中调用这个函数来获取线程请求的临界区。如果此时没有其他线程占用临界区对象,则调用Lock()的线程获取临界区;否则,线程将被挂起并放入系统队列中等待,直到当前拥有临界区的线程释放临界区。
访问临界区后,使用CCriticalSection的成员函数Unlock()释放临界区:critical_section。unlock();
通俗地说,线程A执行到critical_section。lock();语句,如果其他线程(B)正在执行critical_section。lock();在声明和关键部分之后
n. Unlock();语句前的语句时,线程A就会等待,直到线程B执行完critical_section. Unlock();语句,线程A才会继续执行。
下面再通过一个实例进行演示说明。
例程8 MultiThread8
建立一个基于对话框的工程MultiThread8,在对话框IDD_MULTITHREAD8_DIALOG中加入两个按钮和两个编辑框控件,两个按钮的ID分别为IDC_WRITEW和IDC_WRITED,标题分别为“写‘W’”和“写‘D’”;两个编辑框的ID分别为IDC_W和IDC_D,属性都选中Read-only;
在MultiThread8Dlg.h文件中声明两个线程函数: UINT WriteW(LPVOID pParam);
UINT WriteD(LPVOID pParam);
使用ClassWizard分别给IDC_W和IDC_D添加CEdit类变量m_ctrlW和m_ctrlD;
在MultiThread8Dlg.cpp文件中添加如下内容:
为了文件中能够正确使用同步类,在文件开头添加:#include "afxmt.h"
定义临界区和一个字符数组,为了能够在不同线程间使用,定义为全局变量:CCriticalSection critical_section;
char g_Array[10];
添加线程函数:UINT WriteW(LPVOID pParam)
{
CEdit *pEdit=(CEdit*)pParam;
pEdit- SetWindowText("");
critical_section.Lock();
//锁定临界区,其它线程遇到critical_section.Lock();语句时要等待
//直至执行critical_section.Unlock();语句
for(int i=0;i i++)
{
g_Array[i]=W;
pEdit- SetWindowText(g_Array);
Sleep(1000);
}
critical_section.Unlock();
return 0;
}
UINT WriteD(LPVOID pParam)
{
CEdit *pEdit=(CEdit*)pParam;
pEdit- SetWindowText("");
critical_section.Lock();
//锁定临界区,其它线程遇到critical_section.Lock();语句时要等待
//直至执行critical_section.Unlock();语句
for(int i=0;i i++)
{
g_Array[i]=D;
pEdit- SetWindowText(g_Array);
Sleep(1000);
}
critical_section.Unlock();
return 0;
}
分别双击按钮IDC_WRITEW和IDC_WRITED,添加其响应函数: void CMultiThread8Dlg::OnWritew()
{
CWinThread *pWriteW=AfxBeginThread(WriteW,
m_ctrlW,
THREAD_PRIORITY_NORMAL,
0,
CREATE_SUSPENDED);
pWriteW- ResumeThread();
}
void CMultiThread8Dlg::OnWrited()
{
CWinThread *pWriteD=AfxBeginThread(WriteD,
m_ctrlD,
THREAD_PRIORITY_NORMAL,
0,
CREATE_SUSPENDED);
pWriteD- ResumeThread();
}
由于代码较简单,不再详述。编译、运行该例程,您可以连续点击两个按钮,观察体会临界类的作用。
B、使用 CEvent 类
CEvent 类提供了对事件的支持。事件是一个允许一个线程在某种情况发生时,唤醒另外一个线程的同步对象。例如在某些网络应用程序中,一个线程(记为A)负责监听通讯端口,另外一个线程(记为B)负责更新用户数据。通过使用CEvent 类,线程A可以通知线程B何时更新用户数据。每一个CEvent 对象可以有两种状态:有信号状态和无信号状态。线程监视位于其中的CEvent 类对象的状态,并在相应的时候采取相应的操作。
在MFC中,CEvent 类对象有两种类型:人工事件和自动事件。一个自动CEvent 对象在被至少一个线程释放后会自动返回到无信号状态;而人工事件对象获得信号后,释放可利用线程,但直到调用成员函数ReSetEvent()才将其设置为无信号状态。在创建CEvent 类的对象时,默认创建的是自动事件。 CEvent 类的各成员函数的原型和参数说明如下:
1、CEvent(BOOL bInitiallyOwn=FALSE,
BOOL bManualReset=FALSE,
LPCTSTR lpszName=NULL,
LPSECURITY_ATTRIBUTES lpsaAttribute=NULL);
bInitiallyOwn:指定事件对象初始化状态,TRUE为有信号,FALSE为无信号;
bManualReset:指定要创建的事件是属于人工事件还是自动事件。TRUE为人工事件,FALSE为自动事件;
后两个参数一般设为NULL,在此不作过多说明。
2、BOOL CEvent::SetEvent();
将 CEvent 类对象的状态设置为有信号状态。如果事件是人工事件,则 CEvent 类对象保持为有信号状态,直到调用成员函数ResetEvent()将 其重新设为无信号状态时为止。如果CEvent 类对象为自动事件,则在SetEvent()将事件设置为有信号状态后,CEvent 类对象由系统自动重置为无信号状态。
如果该函数执行成功,则返回非零值,否则返回零。 3、BOOL CEvent::ResetEvent();
该函数将事件的状态设置为无信号状态,并保持该状态直至SetEvent()被调用时为止。由于自动事件是由系统自动重置,故自动事件不需要调用该函数。如果该函数执行成功,返回非零值,否则返回零。我们一般通过调用WaitForSingleObject函数来监视事件状态。前面我们已经介绍了该函数。由于语言描述的原因,CEvent 类的理解确实有些难度,但您只要通过仔细玩味下面例程,多看几遍就可理解。
例程9 MultiThread9
建立一个基于对话框的工程MultiThread9,在对话框IDD_MULTITHREAD9_DIALOG中加入一个按钮和两个编辑框控件,按钮的ID为IDC_WRITEW,标题为“写‘W’”;两个编辑框的ID分别为IDC_W和IDC_D,属性都选中Read-only;
在MultiThread9Dlg.h文件中声明两个线程函数: UINT WriteW(LPVOID pParam);
UINT WriteD(LPVOID pParam);
使用ClassWizard分别给IDC_W和IDC_D添加CEdit类变量m_ctrlW和m_ctrlD;
在MultiThread9Dlg.cpp文件中添加如下内容:
为了文件中能够正确使用同步类,在文件开头添加
#include "afxmt.h"
定义事件对象和一个字符数组,为了能够在不同线程间使用,定义为全局变量。 CEvent eventWriteD;
char g_Array[10];
添加线程函数: UINT WriteW(LPVOID pParam)
{
CEdit *pEdit=(CEdit*)pParam;
pEdit- SetWindowText("");
for(int i=0;i i++)
{
g_Array[i]=W;
pEdit- SetWindowText(g_Array);
Sleep(1000);
}
eventWriteD.SetEvent();
return 0;
}
UINT WriteD(LPVOID pParam)
{
CEdit *pEdit=(CEdit*)pParam;
pEdit- SetWindowText("");
WaitForSingleObject(eventWriteD.m_hObject,INFINITE);
for(int i=0;i i++)
{
g_Array[i]=D;
pEdit- SetWindowText(g_Array);
Sleep(1000);
}
return 0;
}
仔细分析这两个线程函数, 您就会正确理解CEvent 类。线程WriteD执行到 WaitForSingleObject(eventWriteD.m_hObject,INFINITE);处等待,直到事件eventWriteD为有信号该线程才往下执行,因为eventWriteD对象是自动事件,则当WaitForSingleObject()返回时,系统自动把eventWriteD对象重置为无信号状态。
双击按钮IDC_WRITEW,添加其响应函数: void CMultiThread9Dlg::OnWritew()
{
CWinThread *pWriteW=AfxBeginThread(WriteW,
m_ctrlW,
THREAD_PRIORITY_NORMAL,
0,
CREATE_SUSPENDED);
pWriteW- ResumeThread();
CWinThread *pWriteD=AfxBeginThread(WriteD,
m_ctrlD,
THREAD_PRIORITY_NORMAL,
0,
CREATE_SUSPENDED);
pWriteD- ResumeThread();
}
编译并运行程序,单击“写‘W’”按钮,体会事件对象的作用。
C、使用CMutex 类
互斥对象与临界区对象很像.互斥对象与临界区对象的不同在于:互斥对象可以在进程间使用,而临界区对象只能在同一进程的各线程间使用。当然,互斥对象也可以用于同一进程的各个线程间,但是在这种情况下,使用临界区会更节省系统资源,更有效率。
D、使用CSemaphore 类
当需要一个计数器来限制可以使用某个线程的数目时,可以使用“信号量”对象。CSemaphore 类的对象保存了对当前访问某一指定资源的线程的计数值,该计数值是当前还可以使用该资源的线程的数目。如果这个计数达到了零,则所有对这个CSemaphore 类对象所控制的资源的访问尝试都被放入到一个队列中等待,直到超时或计数值不为零时为止。一个线程被释放已访问了被保护的资源时,计数值减1;一个线程完成了对被控共享资源的访问时,计数值增1。这个被CSemaphore 类对象所控制的资源可以同时接受访问的最大线程数在该对象的构建函数中指定。
CSemaphore 类的构造函数原型及参数说明如下:
CSemaphore (LONG lInitialCount=1,
LONG lMaxCount=1,
LPCTSTR pstrName=NULL,
LPSECURITY_ATTRIBUTES lpsaAttributes=NULL);
lInitialCount:信号量对象的初始计数值,即可访问线程数目的初始值;
lMaxCount:信号量对象计数值的最大值,该参数决定了同一时刻可访问由信号量保护的资源的线程最大数目;
后两个参数在同一进程中使用一般为NULL,不作过多讨论;
在用CSemaphore 类的构造函数创建信号量对象时要同时指出允许的最大资源计数和当前可用资源计数。一般是将当前可用资源计数设置为最大资源计数,每增加一个线程对共享资源的访问,当前可用资源计数就会减1,只要当前可用资源计数是大于0的,就可以发出信号量信号。但是当前可用计数减小到0时,则说明当前占用资源的线程数已经达到了所允许的最大数目,不能再允许其它线程的进入,此时的信号量信号将无法发出。线程在处理完共享资源后,应在离开的同时通过ReleaseSemaphore()函数将当前可用资源数加1。
下面给出一个简单实例来说明 CSemaphore 类的用法。
例程10 MultiThread10
建立一个基于对话框的工程MultiThread10,在对话框IDD_MULTITHREAD10_DIALOG中加入一个按钮和三个编辑框控件,按钮的ID为IDC_START,标题为“同时写‘A’、‘B’、‘C’”;三个编辑框的ID分别为IDC_A、IDC_B和IDC_C,属性都选中Read-only;
在MultiThread10Dlg.h文件中声明两个线程函数: UINT WriteA(LPVOID pParam);
UINT WriteB(LPVOID pParam);
UINT WriteC(LPVOID pParam);
使用ClassWizard分别给IDC_A、IDC_B和IDC_C添加CEdit类变量m_ctrlA、m_ctrlB和m_ctrlC;
在MultiThread10Dlg.cpp文件中添加如下内容:
为了文件中能够正确使用同步类,在文件开头添加:
#include "afxmt.h"
定义信号量对象和一个字符数组,为了能够在不同线程间使用,定义为全局变量:CSemaphore semaphoreWrite(2,2); //资源最多访问线程2个,当前可访问线程数2个
char g_Array[10];
添加三个线程函数:
UINT WriteA(LPVOID pParam)
{
CEdit *pEdit=(CEdit*)pParam;
pEdit- SetWindowText("");
WaitForSingleObject(semaphoreWrite.m_hObject,INFINITE);
CString str;
for(int i=0;i i++)
{
pEdit- GetWindowText(str);
g_Array[i]=A;
str=str+g_Array[i];
pEdit- SetWindowText(str);
Sleep(1000);
}
ReleaseSemaphore(semaphoreWrite.m_hObject,1,NULL);
return 0;
}
UINT WriteB(LPVOID pParam)
{
CEdit *pEdit=(CEdit*)pParam;
pEdit- SetWindowText("");
WaitForSingleObject(semaphoreWrite.m_hObject,INFINITE);
CString str;
for(int i=0;i i++)
{
pEdit- GetWindowText(str);
g_Array[i]=B;
str=str+g_Array[i];
pEdit- SetWindowText(str);
Sleep(1000);
}
ReleaseSemaphore(semaphoreWrite.m_hObject,1,NULL);
return 0;
}
UINT WriteC(LPVOID pParam)
{
CEdit *pEdit=(CEdit*)pParam;
pEdit- SetWindowText("");
WaitForSingleObject(semaphoreWrite.m_hObject,INFINITE);
for(int i=0;i i++)
{
g_Array[i]=C;
pEdit- SetWindowText(g_Array);
Sleep(1000);
}
ReleaseSemaphore(semaphoreWrite.m_hObject,1,NULL);
return 0;
}
这三个线程函数不再多说。在信号量对象有信号的状态下,线程执行到WaitForSingleObject语句处继续执行,同时可用线程数减1;若线程执行到WaitForSingleObject语句时信号量对象无信号,线程就在这里等待,直到信号量对象有信号线程才往下执行。
双击按钮IDC_STAR。
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。