Windows环境下的多线程编程浅讲
这是一个简单的关于多线程编程的指南,任何初学者都能在这里找到关于编写简单多线程程序的方法。如果需要更多的关于这方面的知识可以参考MSDN或本文最后列出的参考文献。本文将以C语言作为编程语言,在Lcc-Win32编译器下进行编译调试,使用MS-Visual Studio的人可以参考其相关的使用手册。没有C语言基础的朋友可以找一本简单的教程看一下,相信会是有帮助的。
首先解释什么是线程,谈到这个就不得不说一下什么是进程,这是两个相关的概念。为了方便理解,我推荐大家下载并使用一下flashget(网际快车)这个下载软件,当然迅雷也行,只是不如flashget这么直观了。O.K.言归正传,当我们打开flashget这个软件时,就是启动了一个进程。这个进程就指flashget这个软件的运行状态。当我们用它来下载时都是在整个flashget软件里操作,所以可以把单个进程看作整个程序的最外层。然后我们可以看到,在用flashget下载时可以选择同时使用多少线程来下载的选项(这里介绍个小技巧,用超级兔子可以优化这里的选项,将最大的线程数限制从10改为30),这个选项就包含了我所要讲的线程的概念。当同时用比如5根线程来下载的话,flashget就会同时使用5个独立的下载程序来下载文件(各个线程通过某种方式通信,以确定各自所要下载的部分,关于线程间的通信在后面介绍)。所以线程可以看作是在进程框架下独立运行的与进程功能相关的程序。如果将windows看作进程的话,那么里面跑得QQ啊,MSN什么的都可以看作是windows的线程了。当然进程本身也可以看作是线程,只是凌驾于其它线程之上的线程罢了。另外比如我们使用浩方对战平台来网上对战,当用浩方启动魔兽时,由于运行的是两个不同的程序,那就是多进程编程了,这要比多线程复杂点,但也是复杂的有限,有兴趣的人可以参考MSDN上的相关资料。
最后声明一下,文中的例子都没有过多的注释,不过我都使用了最简单的例子(至少我个人已经想不出更简单的了),如果读者对理解这里的程序上有困难,那么我的建议是这篇文章已经不适合你了,你应该看一些更基础的书,比如《小学生基础算数》^_^。
为了使对多线程编程有个更感性的认识,这里先给出一个简单的多线程程序。
(注:这是一个利用标准C99函数实现的初级多线程实例,由于兼容性的问题可能在MS-Visual Studio下无法编译通过,遇到这个问题人可以跳过这个实例的实际运作方式或者改用Lcc-Win32)
//***********一个简单的多线程程序**************** #include
int main(void) { }
//***********************************************
代码的一开始是先指明所要用到的头文件,stdheaders.h是Lcc-Win32专门制作的包含了所有符合C99标准的库函数的头文件。这一点在后面不再指出,使用MS-Visual Studio的人可以自行添入stdio.h等标准头文件。接着是一个子程序,这
int b=0;
for(int i=0;i<30;i++){ }
while(1); return 1;
printf(\beginthread(thread,0,NULL);
while(1){
//这里可以加入一些你所希望线程执行的程序,比如: //puts(\//break; }
endthread();
个就是一般的线程程序,在C语言中把它作为一个子程序单独写出。再后面就是main函数,它起的作用就好比整个程序的外框架,具体要做什么则有线程来处理。可以看到在main函数和线程函数中都有无限的循环,这是因为:如果mian函数执行完了,那么无论线程函数处于一个怎样的状态整个程序都会退出,而如果线程中没有无限循环,那么线程的自动结束并不会影响到main函数,是否要结束main函数可以根据实际需要。在main函数中有一个beginthread()函数,这个就是专门用来启动线程的函数,可以看到这个函数的第一个参数就是需要启动的线程的函数地址,不过本文是讨论如何在windows下实现多线程编程,就不对这个函数进行介绍了,而把更多的精力放到对后面windowsAPI(从C语言的观点来开,就是函数)的介绍上,并且beginthread()这个函数过于轻量级,在略微复杂一点的场合下就不能满足要求了。如果执行这个程序并把'//'符号去掉的话,可以发现屏幕的输出毫无章法,这是由于操作系统安时间片给每个线程分配执行时间,当一个线程还没执行完毕,但是分给它的时间片用完了,只得暂时停止工作,等到下一个分给它的时间片时继续工作,这就产生了屏幕上杂乱的结果(即各个线程交错执行的结果),要解决这个问题要关系到线程的同步,这将在后面介绍。
1 windows中几个常用多线程的函数介绍
======================================================= CreateThread() //创建一个新的线程
CreateRemoteThread() //在另一个进程中创建一个线程(不在本文讨论范围内) ExitThread() //正常的结束一个线程的执行
TerminateThread() //立即中止线程的执行(理论上最好不去使用) GetExitCodeThread() //得到线程结束时的退出码 Get/SetThreadPriority() //得到/设置线程的优先级 Suspend/ResumeThread() //挂起/启动一个线程 CloseHandle() //关闭一个线程的句柄
======================================================= 下面对其简要介绍
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes, //线程安全属性 DWORD dwStackSize, //线程堆栈的初始大小,一般设为0,使用默认设置 LPTHREAD_START_ROUTINE lpStartAddress, //线程函数的起始地址 LPVOID lpParameter, //线程函数的参数 DWORD dwCreationFlags, //创建方式 LPDWORD lpThreadId //线程标示符 };
‘lpThreadAttributes’作为线程安全属性设置参数,一般设为NULL,表示线程返回的句柄不能被子进程所继承。
‘dwStackSize’参数一般设为0以使用系统默认值,但是高级用户可以将其设为小于系统默认值以节省开销。
‘lpStartAddress’为线程函数的首地址。
‘lpParameter’为线程函数所需要的参数,并且必须以指针的方式传递,没有参数时可设为NULL。
‘dwCreationFlags’表示当线程创建后是先挂起不执行呢,还是立即执行。如果设为CREATE_SUSPENDED则先不执行,等待ResumeThread()函数来开启线程的执行,如果是0,则立即执行。
‘lpThreadId’是线程的标示号,每个线程的号不同,由操作系统设置,所以这个参数只要提供空间,而不需要赋值。通常设为NULL。
最后如果HANDLE返回NULL,表示出错了。(注:各类handle在windows的C观点来开只是一个void型指针,占用32bit空间,用来标示不同的API对象。) 一个常见的该函数可以这样写: #include
DWORD WINAPI ThreadFun(LPVOID IpParameter) //线程函数
{ }
void main(void) {
DWORD dw; HANDLE handle;
handle=CreateThread(NULL,0,ThreadFun,NULL,0,NULL);//error check is char ch; while(1) { }
if((ch=getchar())=='\\n') { }
printf(\Sleep(500);
very important while(1) {
if(handle==NULL) { }
printf(\主线程在运行!\\n\Sleep(1000);
puts(\
} }
*****************************************