第二讲线程的基本使用Word下载.docx
- 文档编号:17890230
- 上传时间:2022-12-11
- 格式:DOCX
- 页数:11
- 大小:78.66KB
第二讲线程的基本使用Word下载.docx
《第二讲线程的基本使用Word下载.docx》由会员分享,可在线阅读,更多相关《第二讲线程的基本使用Word下载.docx(11页珍藏版)》请在冰豆网上搜索。
而不同的进程之间是不能够直接共享资源的。
比较经典的情况是进程中的多个线程执行相同的代码,并共享进程中的变量。
例如上述笔者提到过的多个通道同时检票。
当然这种情况是不共享变量的,因为每个检票员的手上都有一个检票钳。
共享变量的情况可以类似于厨师做菜。
几个厨师按照相同的菜谱做菜,他们共同使用一些食材,每个厨师对食材的使用情况会影响其他厨师的工作。
在目前的CPU中,多个线程同时运行,每个处理器或者核心运行一个线程。
【创建线程】
在一些复杂的信息处理系统中通常为了提高应用程序的并发能力,都会采用多线程的编程方法。
那么在MFC架构中如何创建一个线程呢?
首先让我们来了解一下“线程函数”的概念。
线程函数:
每一个线程必须拥有一个进入点函数,线程从这个进入点开始运行。
主线程的进入点函数为主函数main(),如果想在进程中创建一个辅助线程,也必须为该辅助线程指定一个进入点函数,这个函数也被称之为“线程函数”。
线程函数的声明格式如下:
DWORDWINAPIThread_Function_Name(LPVOIDParameter_Name)
{
//函数体
}
其中,DWORDWINAPI说明线程函数的类型是不可变的,而Thread_Function_Name则说明了线程的名称,是可以由程序员自己指定的,LPVOID类型是一个void*类型的指针,用于提供线程和外界通信的通道。
而函数体中则说明该线程具体要执行的代码。
创建线程:
在MFC中通常我们使用CreateThread函数创建一个新的线程。
函数原型如下:
HANDLECreateThread(
SECURITY_ATTRIBUTES*lpThreadAttributes,
SIZE_TdwStackSize,
LPTHREAD_START_ROUTINElpStartAddress,
LPVOIDlpParameter,
DWORDdwCreationFlags,
DWORD*lpThreadId
)
其中:
IpThreadAttributes指向LPSCURITY_ATTRUBUTES的结构体指针,用于决定CreateThread函数返回的句柄是否可以被子进程所继承,通常我们设置该参数为NULL;
dwStackSize用于指定线程堆栈的初始大小,单位为字节,通常我们也设置为NULL;
IpStartAddress指定线程函数的起始地址,通常直接填写线程函数的名称即可;
IpParameter指定传递给线程函数的参数;
dwCreationFlags指定线程在创建后是否立即启动,如果指定为CREATE_SUSPENDED则线程创建后进入挂起状态,而如果将该参数指定为0或者NULL则线程在创建后将立即启动。
IpThreadId参数是一个指向DWORD(unsignedlong)类型的参数指针,用于获取线程的ID,即TID(线程编号)。
在Windows操作系统中,每一个进程中的线程都有一个唯一的TID。
小提示:
在MFC中通常带有指针的类型均已“LP”开头,例如LPVOID即“void*”类型。
DWORD是指双字类型,通常1word=2B,即两个字节,而DWORD即4个字节。
在MFC中通常将unsignedlong类型指定为DWORD类型。
[例]线程创建实例
#include<
iostream>
Windows.h>
conio.h>
usingnamespacestd;
DWORDWINAPIThread(LPVOIDt)//线程函数
cout<
<
"
现在运行的是线程"
(int)t<
endl;
return0;
voidmain()
DWORDThreadID;
//线程ID
for(inti=0;
i<
10;
i++)//创建10个线程
{
:
:
CreateThread(0,0,Thread,(LPVOID)i,0,&
ThreadID);
}
getch();
两次运行的效果如下:
从上面的结果我们可以发现,虽然在创建线程时按照1-10的顺序来传递参数,但是在线程中的输出顺序却被打乱了,这说明这些线程几乎是同时被执行的,但是第二张图也很生动的说明了他们的执行顺序,这就说明了在多线程的编程中并不是当一个线程执行完了之后再去创建另外一个线程的,童鞋们可以尝试多次运行该程序你会发现每次的输出结果都是不尽相同的。
[例]使用线程实现动态的时钟刷新。
我们也可以利用多线程的原则,让一个线程为我们时刻获取当前的时间,并将它们不断的通过刷新屏幕输出出来,从而达到类似于电子钟的目的。
代码如下:
afx.h>
DWORDWINAPIClock(LPVOIDi)//辅助线程函数
CTimetime;
while
(1)
system("
cls"
);
//清除上一次输出的结果
time=CTime:
GetCurrentTime();
cout<
当前时间为:
time.GetHour()<
;
time.GetMinute()<
time.GetSecond()<
voidmain()//主线程
//线程的TID
:
CreateThread(0,0,Clock,0,0,&
//创建线程
//终止进程停止,如果进程停止,所有的线程都会终止
由于是动态效果,在纸上无法为大家呈现,故此只好截一下图了。
运行效果如下:
【UsageCount成员】
使用计数(UsageCount)成员是线程内核对象中的重要成员,它记录了线程的使用次数,这个计数说明了次内核对象被打开的次数。
线程的存在和该成员是密切相关的,当这个值为0时,系统就默认该线程已经没有任何需要再次执行的价值了,从而释放该线程在内存中所占用的空间。
当创建新的线程时,UsageCount成员默认值为2,只要线程没有结束运行,UsageCount的值就至少为1,。
在创建一个新的线程时,CreateThread函数返回了线程的句柄,就相当于打开了一次新创建的线程,这也会促使UsageCount的值自动加1,。
所以创建一个新的线程后,它的默认值为2,只要有进程打开该线程,就会使得UsageCount的值加1。
【打开线程】
在MFC中我们可以借助OpenThread函数手动打开一个线程,此时UsageCount的值会自动加1,在使用OpenThread函数打开线程之后我们还需要使用CloseHandle函数关闭线程,使得线程的UsageCount成员减1。
OpenThread函数的声明如下:
HANDLEWINAPIOpenThread(
DWORDdwDesiredAccess,//线程对资源的访问权限,可设置为THREAD_ALL_ACCESS
BOOLbInheritHandle,//指定此函数返回的句柄是否可以被子进程继承,通常为false
DWORDdwThreadId//目标线程的ID号,即TID
[例]线程打开和关闭案例
DWORDWINAPIThread(LPVOIDi)//辅助线程函数
这是一个线程!
HANDLEThreadHandle;
ThreadHandle=:
CreateThread(0,0,Thread,0,0,&
//创建线程并获取句柄
OpenThread(THREAD_ALL_ACCESS,false,ThreadID);
//打开线程
CloseHandle(ThreadHandle);
这里值得一提的是当系统创建一个线程时,UsageCount的值会初始化为2,当线程函数中的代码执行完成之后,线程的生命周期也就由此结束了,此时UsageCount的值便会自动减1,接下来调用CloseHandle函数就会再使UsageCount的值减1,此时UsageCount的值为0,系统即可撤销该对象所占用的内存,释放资源。
如果不使用CloseHandle关闭线程的话,那么线程的UsageCount对象将永远不能为0,系统将永远不会撤销它所占用的内存,这就会造成内存泄露。
当然,一旦进程全部结束之后,进程中所有的线程也会跟着一起消亡,该进程中所有线程(包括主线程)所占用的资源也会全部被释放。
【线程的终止】
那么如何终止一个线程呢?
终止一个线程的执行共有4种方式,在此笔者仅介绍常用的两种。
线程的自然退出:
线程函数中的代码执行到return语句时,Windows将终止线程的执行。
一般建议使用这种方法退出线程。
这种退出方式就好像是一个人正常的从工作到退休的过程,一切按部就班。
使用ExitThread函数终止线程:
当然,如果你希望手动终止某一个线程,并且保证它占用的资源也得到释放,不会出现内存泄露的情况的话,你可以使用ExitThread函数来终止当前线程的运行,促使系统释放所有被此线程占用的资源。
ExitThread函数声明如下:
voidExitThread(DWORDdwExitCode);
其中dwExitCode为线程的退出码,可以通过GetExitCodeThread函数获取该线程的退出码。
当线程在运行期间时,线程的退出码为STILI_ACTIVE。
线程运行结束后,线程的ExitCode将自动的为线程函数的返回值。
GetExitCodeThread函数的声明如下:
BOOLGetExitCodeThread(HANDLEhThread,LPDWORDlpExitCode);
其中hTread是线程的句柄,IpExitCode是用于保存线程退出码的指针。
[例]使用ExitThread函数关闭线程的实例
time.h>
3;
i++)
Sleep(1000);
//暂停1s
//返回退出码
DWORDExitCode;
//线程的退出码
do
if(:
GetExitCodeThread(ThreadHandle,&
ExitCode)==true)//如果成功获取了退出码
{
cout<
线程退出码:
ExitCode<
if(ExitCode==STILL_ACTIVE)//如果线程一直在运行
{
cout<
目标线程仍在运行中!
}
else
:
ExitThread(ExitCode);
//彻底结束该线程,并释放资源
}
}while
(1);
【SuspendCount成员】
线程对象中的挂起计数(SuspendCount)成员用于指明线程的挂起次数。
当线程被创建时,其内核对象的挂起计数被初始化为1,。
这可以阻止新创建的线程被调度到CPU中,因为线程的初始化需要很长的时间,当线程初始化结束后,线程处于挂起状态。
当挂起计数为0时,该线程就处于可调度状态。
【挂起线程】
在MFC中我们可以使用SuspendThread函数挂起一个线程,函数的原型如下:
DWORDWINAPISuspendThread(HANDLEhThread);
其中hThread指代了需要挂起的线程的句柄。
调用SuspendThread函数会增加线程的挂起次数,任何一个线程都可以调用该函数来暂停另一个线程的运行。
[例]线程挂起实例
DWORDWINAPIThread1(LPVOIDt);
//声明线程函数
DWORDWINAPIThread2(LPVOIDhandle);
DWORDThreadID1;
DWORDThreadID2;
HANDLEhandle;
handle=:
CreateThread(0,0,Thread1,0,0,&
ThreadID1);
//创建线程1
CreateThread(0,0,Thread2,(LPVOID)handle,0,&
ThreadID2);
//创建线程2
//暂停进程
DWORDWINAPIThread1(LPVOIDt)
线程1"
//将0作为结束码
DWORDWINAPIThread2(LPVOIDhandle)
HANDLEh=(HANDLE)handle;
线程2"
SuspendThread(h);
//挂起线程1
return1;
//将1作为结束码
【再运行线程】
当我们需要再运行挂起的线程时,可以调用ResumeThread函数将其设置为可调度状态,函数原型如下:
DWORDWINAPIResumeThread(HANDLEhThread);
调用该函数会减少线程的挂起计数,当计数值为0时,线程被恢复到运行状态。
如果调度成功,函数返回线程的前一个挂起计数值,否则返回0xFFFFFFFF。
单个线程可以被挂起若干次。
如果一个线程被挂起三次,则必须被唤醒三次才可以被分配CPU资源。
每隔20ms,Windows操作系统便会检查当前存在的所有线程对象,然后选择一个可以调度的线程对象,将其CONTEXT(上下文)装入CPU的寄存器,这一操作称之为上下文转换。
CONTEXT成员是每一个线程所拥有的一组CPU寄存器,称之为线程上下文。
这组寄存器保存在一个CONTEXT结构中,它反映了该线程上次运行时CPU寄存器的状态。
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 第二 线程 基本 使用