C++多线程编程入门Word格式文档下载.docx
《C++多线程编程入门Word格式文档下载.docx》由会员分享,可在线阅读,更多相关《C++多线程编程入门Word格式文档下载.docx(13页珍藏版)》请在冰豆网上搜索。
网络应用和嵌入式应用。
为什么在这两个领域应用较多呢?
因为多线程应用能够解决两大问题:
并发。
网络程序具有天生的并发性。
比如网络数据库可能需要同时处理数以千计的请求。
而由于网络连接的时延不确定性和不可靠性,一旦等待一次网络交互,可以让当前线程进入睡眠,退出调度,处理其他线程。
这样就能够有效利用系统资源,充分发挥系统实时处理能力。
线程的切换是轻量级的,所以可以保证足够快。
每当有事件发生,状态改变,都能有线程及时响应,而且每次线程内部处理的计算强度和复杂度都不大。
在这种情况下,多线程实现的模型也是高效的。
在有些语言中,对多线程或者并发的支持是直接内建在语言中的,比如Ada和VHDL。
在C++里面,对多线程的支持由具体操作系统提供的函数接口支持。
不同的系统中具体实现方法不同。
后面所有例子只给出windows和Unix/Linux的实现。
在后面的实现中,考虑的是尽量封装隔离底层的多线程函数接口,屏蔽操作系统底层的线程实现具体细节,介绍的重点是多线程编程中较通用的概念。
同时也尽量体现C++面向对象的一面。
最后,由于空闲时间有限,我只求示例代码能够明确表达自己的意思即可。
至于代码的尽善尽美就只能有劳各位尽力以为之了。
第2节线程的创建
本节介绍如下内容:
线程状态、线程运行环境、线程类定义
示例程序:
线程类的Windows和Unix实现
线程状态
在一个线程的生存期内,可以在多种状态之间转换。
不同操作系统可以实现不同的线程模型,定义许多不同的线程状态,每个状态还可以包含多个子状态。
但大体说来,如下几种状态是通用的:
就绪:
参与调度,等待被执行。
一旦被调度选中,立即开始执行。
运行:
占用CPU,正在运行中。
休眠:
暂不参与调度,等待特定事件发生。
中止:
已经运行完毕,等待回收线程资源(要注意,这个很容易误解,后面解释)。
线程环境
线程存在于进程之中。
进程内所有全局资源对于内部每个线程均是可见的。
进程内典型全局资源有如下几种:
代码区。
这意味着当前进程空间内所有可见的函数代码,对于每个线程来说也是可见的。
静态存储区。
全局变量。
静态变量。
动态存储区。
也就是堆空间。
线程内典型的局部资源有:
本地栈空间。
存放本线程的函数调用栈,函数内部的局部变量等。
部分寄存器变量。
例如本线程下一步要执行代码的指针偏移量。
一个进程发起之后,会首先生成一个缺省的线程,通常称这个线程为主线程。
C/C++程序中主线程就是通过main函数进入的线程。
由主线程衍生的线程称为从线程,从线程也可以有自己的入口函数,作用相当于主线程的main函数。
这个函数由用户指定。
Pthread和winapi中都是通过传入函数指针实现。
在指定线程入口函数时,也可以指定入口函数的参数。
就像main函数有固定的格式要求一样,线程的入口函数一般也有固定的格式要求,参数通常都是void*类型,返回类型在pthread中是void*,winapi中是unsignedint,而且都需要是全局函数。
最常见的线程模型中,除主线程较为特殊之外,其他线程一旦被创建,相互之间就是对等关系(peertopeer),不存在隐含的层次关系。
每个进程可以创建的最大线程数由具体实现决定。
为了更好的理解上述概念,下面通过具体代码来详细说明。
线程类接口定义。
一个线程类无论具体执行什么任务,其基本的共性无非就是:
创建并启动线程、停止线程。
另外还有就是能睡,能等,能分离执行(有点拗口,后面再解释)。
还有其他的可以继续加…
将线程的概念加以抽象,可以为其定义如下的类:
文件thread.h
#ifndef__THREAD__H_
#define__THREAD__H_
classThread
{
public:
Thread();
virtual~Thread();
intstart(void*=NULL);
voidstop();
voidsleep(int);
voiddetach();
void*wait();
protected:
virtualvoid*run(void*)=0;
private:
//这部分win和unix略有不同,先不定义,后面再分别实现。
//顺便提一下,我很不习惯写中文注释,这里为了更明白一
//点还是选用中文。
…
};
#endif
Thread:
:
start()函数是线程启动函数,其输入参数是无类型指针。
stop()函数中止当前线程。
sleep()函数让当前线程休眠给定时间,单位为秒。
run()函数是用于实现线程类的线程函数调用。
detach()和thread:
wait()函数涉及的概念略复杂一些。
在稍后再做解释。
Thread类是一个虚基类,派生类可以重载自己的线程函数。
下面是一个例子。
示例程序
代码写的都不够精致,暴力类型转换比较多,欢迎有闲阶级美化,谢过了先。
文件create.h
#ifndef__CREATOR__H_
#define__CREATOR__H_
#include<
stdio.h>
#include"
thread.h"
classCreate:
publicThread
void*run(void*param)
char*msg=(char*)param;
printf("
%s\n"
msg);
//sleep(100);
可以试着取消这行注释,看看结果有什么不同。
printf("
Onedaypast.\n"
);
returnNULL;
}
然后,实现一个main函数,来看看具体效果:
文件Genesis.cpp
create.h"
intmain(intargc,char**argv)
Createmonday;
Createtuesday;
printf("
AtthefirstGodmadetheheavenandtheearth.\n"
monday.start("
Namingthelight,Day,andthedark,Night,thefirstday."
tuesday.start("
GavethearchthenameofHeaven,thesecondday."
Thesearethegenerationsoftheheavenandtheearth.\n"
return0;
编译运行,程序输出如下:
AtthefirstGodmadetheheavenandtheearth.
Thesearethegenerationsoftheheavenandtheearth.
令人惊奇的是,由周一和周二对象创建的子线程似乎并没有执行!
这是为什么呢?
别急,在最后的printf语句之前加上如下语句:
monday.wait();
tuesday.wait();
重新编译运行,新的输出如下:
Namingthelight,Day,andthedark,Night,thefirstday.
Onedaypast.
GavethearchthenameofHeaven,thesecondday.
为了说明这个问题,需要了解前面没有解释的Thread:
detach()和Thread:
wait()两个函数的含义。
无论在windows中,还是Posix中,主线程和子线程的默认关系是:
无论子线程执行完毕与否,一旦主线程执行完毕退出,所有子线程执行都会终止。
这时整个进程结束或僵死(部分线程保持一种终止执行但还未销毁的状态,而进程必须在其所有线程销毁后销毁,这时进程处于僵死状态),在第一个例子的输出中,可以看到子线程还来不及执行完毕,主线程的main()函数就已经执行完毕,从而所有子线程终止。
需要强调的是,线程函数执行完毕退出,或以其他非常方式终止,线程进入终止态(请回顾上面说的线程状态),但千万要记住的是,进入终止态后,为线程分配的系统资源并不一定已经释放,而且可能在系统重启之前,一直都不能释放。
终止态的线程,仍旧作为一个线程实体存在与操作系统中。
(这点在win和unix中是一致的。
)而什么时候销毁线程,取决于线程属性。
通常,这种终止方式并非我们所期望的结果,而且一个潜在的问题是未执行完就终止的子线程,除了作为线程实体占用系统资源之外,其线程函数所拥有的资源(申请的动态内存,打开的文件,打开的网络端口等)也不一定能释放。
所以,针对这个问题,主线程和子线程之间通常定义两种关系:
可会合(joinable)。
这种关系下,主线程需要明确执行等待操作。
在子线程结束后,主线程的等待操作执行完毕,子线程和主线程会合。
这时主线程继续执行等待操作之后的下一步操作。
主线程必须会合可会合的子线程,Thread类中,这个操作通过在主线程的线程函数内部调用子线程对象的wait()函数实现。
这也就是上面加上三个wait()调用后显示正确的原因。
必须强调的是,即使子线程能够在主线程之前执行完毕,进入终止态,也必需显示执行会合操作,否则,系统永远不会主动销毁线程,分配给该线程的系统资源(线程id或句柄,线程管理相关的系统资源)也永远不会释放。
相分离(detached)。
顾名思义,这表示子线程无需和主线程会合,也就是相分离的。
这种情况下,子线程一旦进入终止态,系统立即销毁线程,回收资源。
无需在主线程内调用wait()实现会合。
Thread类中,调用detach()使线程进入detached状态。
这种方式常用在线程数较多的情况,有时让主线程逐个等待子线程结束,或者让主线程安排每个子线程结束的等待顺序,是很困
难或者不可能的。
所以在并发子线程较多的情况下,这种方式也会经常使用。
缺省情况下,创建的线程都是可会合的。
可会合的线程可以通过调用detach()方法变成相分离的线程。
但反向则不行。
UNIX实现
pthread_thandle;
boolstarted;
booldetached;
void*threadFuncParam;
friendvoid*threadFunc(void*);
//pthread中线程函数必须是一个全局函数,为了解决这个问题
//将其声明为静态,以防止此文件之外的代码直接调用这个函数。
//此处实现采用了称为Virtualfriendfunctionidiom的方法。
Staticvoid*threadFunc(void*);
文件thread.cpp
pthread.h>
sys/time.h>
#include“thread.h”
staticvoid*threadFunc(void*threadObject)
Thread*thread=(Thread*)threadObject;
returnthread->
run(thread->
threadFuncParam);
Thread()
started=detached=false;
~Thread()
stop();
boolThread:
start(void*param)
pthread_attr_tattributes;
pthread_attr_init(&
attributes);
if(detached)
pthread_attr_setdetachstate(&
attributes,PTHREAD_CREATE_DETACHED);
threadFuncParam=param;
if(pthread_create(&
handle,&
attributes,threadFunc,this)==0)
started=true;
pthread_attr_destroy(&
attribute);
voidThread:
detach()
if(started&
&
!
detached)
pthread_detach(handle);
detached=true;
void*Thread:
wait()
void*status=NULL;
pthread_join(handle,&
status);
returnstatus;
stop()
pthread_cancel(handle);
detached=true;
sleep(unsignedintmilliSeconds)
timevaltimeout={milliSeconds/1000,millisecond%1000};
select(0,NULL,NULL,NULL,&
timeout);
Windows实现
文件thread.h
#ifndef_THREAD_SPECIFICAL_H__
#define_THREAD_SPECIFICAL_H__
windows.h>
staticunsignedint__stdcallthreadFunction(void*);
classThread{
friendunsignedint__stdcallthreadFunction(void*);
Thread();
virtual~Thread();
intstart(void*=NULL);
void*wait();
voidstop();
voiddetach();
staticvoidsleep(unsignedint);
virtualvoid*run(void*)=0;
HANDLEthreadHandle;
boolstarted;
booldetached;
void*param;
unsignedintthreadID;
stdafx.h"
process.h>
unsignedint__stdcallthreadFunction(void*object)
Thread*thread=(Thread*)object;
return(unsignedint)thread->
param);
started=false;
detached=false;
stop();
intThread:
start(void*pra)
if(!
started)
{
param=pra;
if(threadHandle=(HANDLE)_beginthreadex(NULL,0,threadFunction,this,0,&
threadID))
if(detached)
CloseHandle(threadHandle);
}
returnstarted;
//waitforcurrentthreadtoend.
DWORDstatus=(DWORD)NULL;
if(started&
WaitForSingleObject(threadHandle,INFINITE);
GetExitCodeThread(threadHandle,&
return(void*)status;
TerminateThread(threadHandle,0);
//Closingathreadhandledoesnotterminate
//theassociatedthread.
//Toremoveathreadobject,youmustterminatethethread,
//thencloseallhandlestothethread.
//Thethreadobjectremainsinthesystemuntil
//thethreadhasterminatedandallhandlestoithavebeen
//closedthroughacalltoCloseHandle
sleep(unsignedintdelay)
:
Sleep(delay);
小结
本节的主要目的是帮助入门者建立基本的线程概念,以此为基础,抽象出一个最小接口的通用线程类。
在示例程序部分,初学者可以体会到并行和串行程序执行的差异。
有兴趣的话,大家可以在现有线程类的基础上,做进一步的扩展和尝试。
如果觉得对线程的概念需要进一步细化,大家可以进一步扩展和完善现有Thread类。
想更进一步了解的话,一个建议是,可以去看看其他语言,其他平台的线程库中,线程类抽象了哪些概念。
比如Java,perl等跨平台语言中是如何定义的,微软winapi到dotnet中是如何支持多线程的,其线程类是如何定义的。
这样有助于更好的理解线程的模型和基础概念。
另外,也鼓励大家多动手写写代码,在此基础上尝试写一些代码,也会有助于更好的理解多线程程序的特点。
比如,先开始的线程不一定先结束。
线程的执行可能会交替进行。
把printf替换为cout可能会有新的发现,等等。
每个子线程一旦被创建,就被赋予了自己的生命。
管理不好的话,一只特例独行的猪是非常让人头痛的。
对于初学者而言,编写多线程程序可能会遇到很多令人手足无措的bug。
往往还没到考虑效率,避免死锁等阶段就问题百出,而且很难理解和调试。
这是非常正常的,请不要气馁,后续文章会尽量解释各种常见问题的原