VC学习笔记之四多线程知识Word格式文档下载.docx
- 文档编号:4469601
- 上传时间:2023-05-03
- 格式:DOCX
- 页数:33
- 大小:31.12KB
VC学习笔记之四多线程知识Word格式文档下载.docx
《VC学习笔记之四多线程知识Word格式文档下载.docx》由会员分享,可在线阅读,更多相关《VC学习笔记之四多线程知识Word格式文档下载.docx(33页珍藏版)》请在冰点文库上搜索。
线程有五中状态,分别为运行、挂起、睡眠、阻塞、终止。
当所有线程全部处于阻塞状态时,内核处于空闲模式(Idlemode),这时对CPU的电力供应将减小。
4.2何时生成线程
可以考虑在任何程序处理异步活动时生成新线程。
比如,对多窗口编程时,为每个窗口生成一个线程很有好处。
多数多文档界面的应用程序可以为其每个子窗口创建各自的线程。
线程被分为两种:
用户界面线程和工作者线程(又称为后台线程)。
用户界面线程通常用来处理用户的输入并响应各种事件和消息,事实上,应用程序的主执行线程CWinApp对象就是一个用户界面线程,下面我们将着重讲解工作者线程。
4.3线程的创建
在VC中有很多创建线程的方法,下面我们将一一介绍
4.3.1调用API函数CreateThread
4.3.1.1CreateThread的原型如下:
HANDLECreateThread(
LPSECURITY_ATTRIBUTESlpThreadAttributes,
DWORDdwStackSize,
LPTHREAD_START_ROUTINElpStartAddress,
LPVOIDlpParameter,
DWORDdwCreationFlags,//creationflags
LPDWORDlpThreadId
);
其中:
lpThreadAttributes:
表示创建线程的安全属性,即线程的访问权限,它决定谁共享此对象且是否其他进程可以修改此线程,NT下有用。
除非用户线程是需要继承的,否则不需要设置它,在调用的时候直接用NULL代替就可以了。
dwStackSize:
指定线程栈的尺寸,如果为0则与进程主线程栈相同。
lpStartAddress:
指定线程开始运行的地址,即此线程所要调用的函数,是LPTHREAD_START_ROUTINE结构。
lpParameter:
表示传递给线程的32位的参数,即传递给函数的值,没有则为NULL。
dwCreateFlages:
如果它的值为STACK_SIZE_PARAM_IS_A_RESERVATION,那么参数2可以指定栈的大小,内核将按照参数2的数值来为此线程拥有的栈保留地址空间;
如果它的值不为STACK_SIZE_PARAM_IS_A_RESERVATION,那么参数2必须设置为0。
此参数的值还可以为0、CREATE_SUSPENDED。
CREATE_SUSPENDED表示这个线程在创建后一直处于挂起状态,直到用ResumeThread函数来恢复。
最后一个参数保存函数返回的创建的线程ID。
lpThreadId用来存放返回的线程ID。
4.3.1.2线程的优先级别
进程的每个优先级类包含了五个线程的优先级水平。
在进程的优先级类确定之后,可以改变线程的优先级水平。
以下的CwinThread类的成员函数用于线程优先级的操作:
intGetThreadPriority(HANDLEhThread);
BOOLSetThradPriority()(intnPriority);
上述的二个函数分别用来获取和设置线程的优先级,这里的优先级,是相对于该线程所处的优先权层次而言的,处于同一优先权层次的线程,优先级高的线程先运行;
处于不同优先权层次上的线程,谁的优先权层次高,谁先运行。
要注意的是要想设置线程的优先级,这个线程在创建时必须具有THREAD_SET_INFORMATION访问权限。
对于线程的优先权层次的设置,CwinThread类没有提供相应的函数,但是可以通过Win32SDK函数GetPriorityClass()和SetPriorityClass()来实现。
用SetPriorityClass设置进程优先级类,用SetThreadPriority设置线程优先级水平。
4.3.1.3返回值
CreateThread函数返回指向新线程的句柄,如果不能生成线程,句柄将会是NULL。
另外需要强调的一点是:
即使lpStartAddress和lpParameter参数值无效或者指向不能访问的数据,系统仍将创建线程。
在这些情况下,CreateThread函数返回有效句柄,但是新线程立即终止,并返回错误代码。
可以调用函数GetExitCodeThread函数测试线程生存能力,如果该函数返回STILL_ACTIVE,则线程尚未终止。
4.3.1.4销毁线程对象
在线程终止后,线程句柄仍然有效。
为了销毁线程对象,可以通过调用CloseHandle函数来关闭句柄,如果不知一个句柄存在,线程直到最后一个句柄关闭后才会被销毁;
如果用户忘记关闭句柄,当进程终止时,系统会自动销毁线程。
正常情况下,线程运行到它所开始的函数的结尾就会结束,当线程运行到它所开始的函数的结尾,系统自动调用ExitThread函数:
VOIDExitThread(DWORDdwExitCode);
尽管系统自动调用ExitThread函数,如果某些条件迫使线程必须提前结束,用户还可以直接调用此函数结束线程。
4.3.1.5实例
先创建一个线程处理函数:
DWORDThreadProc(LPWORDlpdwParam)
{
inti=1;
CStringss;
while(i>
0)
{
i++;
ss.Format("
i的值为:
%d"
i);
if(i>
1000000)
i=1;
_sleep(1000);
}
returnDWORD(0);
}
接着再创建线程:
voidCThreadDlg:
:
OnButton2()
DWORDthreadID;
CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ThreadProc2,
NULL,0,&
threadID);
4.3.2创建MFC线程
4.3.2.1函数介绍
AfxBeginThread函数原型如下:
CWinThread*AfxBeginThread(//工作者线程
AFX_THREADPROCpfnThreadProc,//线程函数地址
LPVOIDpParam,//线程参数
intnPriority=THREAD_PRIORITY_NORMAL,//线程优先级
UINTnStackSize=0,//线程堆栈大小,默认为1M
DWORDdwCreateFlags=0,
LPSECURITY_ATTRIBUTESlpSecurityAttrs=NULL
CWinThread*AfxBeginThread(//界面线程
CRuntimeClass*pThreadClass,
intnPriority=THREAD_PRIORITY_NORMAL,
UINTnStackSize=0,
LPSECURITY_ATTRIBUTESlpSecurityAttrs=NULL
参数说明:
pfnThreadProc:
线程函数的地址,该参数不能设置为NULL,线程函数必须定义成全局函数或者类的静态成员函数。
例如:
UINTmyThreadFunc(LPVOIDlparam)
或者
classA
public:
staticUINTtaskmain(LPVOIDparam);
BOOLStartTask();
之所以要定义成类的静态成员函数,是因为类的静态成员函数不属于某个类对象,这样在调用函数的时候就不用传递一个额外的this指针。
pThreadClass:
指向从CWinThread派生的子类对象的RUNTIME_CLASS
pParam:
要传递给线程函数的参数
nPriority:
要启动的线程的优先级,默认优先级为THREAD_PRIORITY_NORMAL(普通优先级)。
nStackSize:
新线程的堆栈大小,如果设置为0,则使用默认大小,在应用程序中一般情况下线程的默认堆栈大小为1M
dwCreateFlags:
线程创建标志,该参数可以指定为下列标志:
CREATE_SUSPENDED:
以挂起方式启动线程,如果你在线程启动之前想初始化一些CWinThread类中的一些成员变量。
比如:
m_bAutoDelete或者你的派生类中的成员变量,当初始化完成之后,你可以使用CWinThread类的ResumeThread成员函数来恢复线程的运行,如果把该标志设置为0,则表示立即启动线程。
lpSecurityAttrs:
指向安全描述符的指针,如果使用默认的安全级别只要讲该参数设置为NULL就可以了!
4.3.2.2终止线程
终止线程采用函数AfxEndThread(UINTnExitCode),其中,nExitCode是退出码。
4.3.2.3源代码
while(i<
5)
AfxMessageBox("
nihao"
AfxEndThread(0);
return0;
CWinThread*pThread=AfxBeginThread(myThreadFunc,GetSafeHwnd());
DWORDExitCode;
GetExitCodeThread(pThread->
m_hThread,&
ExitCode);
//检查线程是否结束
if(ExitCode==STILL_ACTIVE)
//运行中
else
//线程已经结束
4.3.3使用相同功能的C运行库函数
几个运行库函数具有Win32线程函数完全相同的功能:
unsignedlong_beginthread(
void(*start_address)(void*),//startfunction
unsignedstack_size,
void*arglist);
void_endthread(void);
void_sleep(unsignedlongulMilliseconds);
_beginthread函数为一些诸如signal等C运行库函数所依赖的线程执行初始化。
规则是一致的:
如果用户程序用C运行库函数操纵线程,那么无论在什么情况下用户只有一个选择,那就是只能用C运行库函数。
如果用户程序使用Win32函数操纵线程,那么就必须使用CreateThread函数和ExitThread函数。
4.4挂起和恢复线程的执行
挂起的线程停止运行,不会由调度程序分配处理器时间。
在其他线程使其恢复运行之前,该线程会一直保持这种状态。
线程通过调用下列函数使另一个线程暂停或者恢复继续执行。
DWORDSuspendThread(HANDLEhThread);
DWORDResumeThread(HANDLEhThread);
一个线程可以在没有接收到恢复继续执行的指令的情况下连续多次挂起,但是每个SuspendThread命令必须最后和一个ResmeThread命令相匹配。
SuspendThread的原形是:
DWORDSuspendThread(HANDLEhThread);
它返回的是线程的前一个暂停记数.线程暂停的次数,可以是MAXIMUM_SUSPEND_COUNT次(在WINNT.H中是127);
SuspendThread与内核方式的执行是异步的,但是在线程恢复运行之前,不会发生用户方式的执行。
调用SuspendThread必须小心,,如果线程试图从堆栈中分配内存,那么该线程将在该线程上设置一个锁,当其他线程试图访问该堆栈时,这些线程的访问就被停止,直到第一个线程恢复运行,只有知道目标线程在干什么时,并且采取强有力的措施避免因暂停线程带耒的问题或死锁状态,SuspendThread才是安全的。
CwinThread类中包含了应用程序悬挂和恢复它所创建的线程的函数,其中SuspendThread()用来悬挂线程,暂停线程的执行;
ResumeThread()用来恢复线程的执行。
如果你对一个线程连续若干次执行SuspendThread(),则需要连续执行相应次的ResumeThread()来恢复线程的运行。
100)
if(i==3)
{
pThread->
SuspendThread();
}
AfxEndThread(NULL);
CWinThread*pThread;
pThread=AfxBeginThread(myThreadFunc,GetSafeHwnd());
虽然线程只可以自己挂起自己而不能自己恢复运行,但线程可以使自己睡眠一段时间然后唤醒。
睡眠指令从调度队列中暂时删除该线程,线程执行被迫延迟,一段时间间隔后再将其放回调度队列,这样就恢复运行了。
此时,睡眠显然要比空循环好,因为它不占用任何CPU时间。
线程调用下列函数暂停执行一段预定的时间:
VOIDSleep(DWORDdwMilliseconds);
DWORDSleepEx(
DWORDdwMilliseconds,
BOOLbAlertable);
扩展的SleepEx函数特别适用于和后台输入输出函数协调工作,并且可以不用待操作完成就用于初始化读写操作。
操作在后台进行。
当该任务结束后,系统通过程序启动一会调函数通知用户。
后台输入/输出(也叫重叠输入/输出)对和诸如磁带机、网络驱动器等慢速设备协同工作而又需要和用户交互的应用程序特别有用。
SleepEx中的布尔参数会使其在指定的睡眠时间尚未结束之前、而重叠的输入/输出操作却已经完成之时,由系统唤醒未到期的线程。
如果SleepEx函数被中断,该函数返回WAIT_IO_COMPLETION,否则如果睡眠时间结束而未中断,该函数返回0。
4.5线程的同步
在程序中使用多线程时,一般很少有多个线程能在其生命期内进行完全独立的操作。
更多的情况是一些线程进行某些处理操作,而其他的线程必须对其处理结果进行了解。
正常情况下对这种处理结果的了解应当在其处理任务完成后进行。
如果不采取适当的措施,其他线程往往会在线程处理任务结束前就去访问处理结果,这就很有可能得到有关处理结果的错误了解。
例如,多个线程同时访问同一个全局变量,如果都是读取操作,则不会出现问题。
如果一个线程负责改变此变量的值,而其他线程负责同时读取变量内容,则不能保证读取到的数据是经过写线程修改后的。
为了确保读线程读取到的是经过修改的变量,就必须在向变量写入数据时禁止其他线程对其的任何访问,直至赋值过程结束后再解除对其他线程的访问限制。
象这种保证线程能了解其他线程任务处理结束后的处理结果而采取的保护措施即为线程同步。
线程同步是一个非常大的话题,包括方方面面的内容。
从大的方面讲,线程的同步可分用户模式的线程同步和内核对象的线程同步两大类。
用户模式中线程的同步方法主要有原子访问和临界区等方法。
其特点是同步速度特别快,适合于对线程运行速度有严格要求的场合。
内核对象的线程同步则主要由事件、等待定时器、信号量以及信号灯等内核对象构成。
由于这种同步机制使用了内核对象,使用时必须将线程从用户模式切换到内核模式,而这种转换一般要耗费近千个CPU周期,因此同步速度较慢,但在适用性上却要远优于用户模式的线程同步方式。
下面我们将对各种线程同步方式进行讲解。
4.5.1临界区对象
临界区(CriticalSection)是一段独占对某些共享资源访问的代码,在任意时刻只允许一个线程对共享资源进行访问。
如果有多个线程试图同时访问临界区,那么在有一个线程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。
临界区在被释放后,其他线程可以继续抢占,并以此达到用原子方式操作共享资源的目的。
临界区在使用时以CRITICAL_SECTION结构对象保护共享资源,并分别用EnterCriticalSection()和LeaveCriticalSection()函数去标识和释放一个临界区。
所用到的CRITICAL_SECTION结构对象必须经过InitializeCriticalSection()的初始化后才能使用,而且必须确保所有线程中的任何试图访问此共享资源的代码都处在此临界区的保护之下。
否则临界区将不会起到应有的作用,共享资源依然有被破坏的可能。
需要特别说明的是:
临界区对象仅能存在于单个进程内部。
下面通过一段代码展示了临界区在保护多线程访问的共享资源中的作用。
通过两个线程来分别对全局变量g_cArray[10]进行写入操作,用临界区结构对象m_csWIlist来保持线程的同步,并在开启线程前对其进行初始化。
为了使实验效果更加明显,体现出临界区的作用,在线程函数对共享资源g_cArray[10]的写入时,以Sleep()函数延迟1毫秒,使其他线程同其抢占CPU的可能性增大。
如果不使用临界区对其进行保护,则共享资源数据将被破坏,而使用临界区对线程保持同步后则可以得到正确的结果。
代码实现清单附下:
classCCritSecLock
CCritSecLock(CRITICAL_SECTION*pcrit)
:
m_pcrit(pcrit)
EnterCriticalSection(pcrit);
~CCritSecLock()
LeaveCriticalSection(m_pcrit);
private:
CRITICAL_SECTION*m_pcrit;
};
/*************************对临界区的操作************************/
//共享资源
charg_cArray[10];
CRITICAL_SECTIONm_csWIlist;
UINTThreadProc10(LPVOIDpParam)
//进入临界区
CCritSecLocklock(&
m_csWIlist);
//对共享资源进行写入操作
for(inti=0;
i<
10;
i++)
g_cArray[i]='
a'
;
Sleep
(1);
UINTThreadProc11(LPVOIDpParam)
CCritSecLocklock(&
m_list);
g_cArray[10-i-1]='
b'
OnButton3()
CRITICAL_SECTIONg_clsCriticalSection;
//初始化环境
InitializeCriticalSection(&
//启动线程
AfxBeginThread(ThreadProc10,NULL);
AfxBeginThread(ThreadProc11,NULL);
//等待计算完毕
Sleep(300);
//输出计算结果
CStringsResult=CString(g_cArray);
AfxMessageBox(sResult);
4.5.2事件内核对象
在前面讲述线程通信时曾使用过事件内核对象来进行线程间的通信,除此之外,事件内核对象也可以通过通知操作的方式来保持线程的同步。
内核对象的线程同步则主要由事件、等待定时器、信号量以及信号灯等内核对象构成。
WaitForSingleObject函数允许线程挂起自己直到特定对象发送其信号。
利用此函数,线程也可以表示该线程希望等待此对象多长时间。
如果等待时间不定,设置间隔为INFINITE。
如果对象已经可用,或者在指定时间内已经到达其信号状态,WaitForSingle
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- VC 学习 笔记 多线程 知识