C 多线程编程入门.doc
《C 多线程编程入门.doc》由会员分享,可在线阅读,更多相关《C 多线程编程入门.doc(10页珍藏版)》请在淘文阁 - 分享文档赚钱的网站上搜索。
1、第1节 背景 为了更好的理解多线程的概念,先对进程,线程的概念背景做一下简单介绍。早期的计算机系统都只允许一个程序独占系统资源,一次只能执行一个程序。在大型机年代,计算能力是一种宝贵资源。对于资源拥有方来说,最好的生财之道自然是将同一资源同时租售给尽可能多的用户。最理想的情况是垄断全球计算市场。所以不难理 解为何当年IBM预测“全球只要有4台计算机就够了”。 这种背景下,一个计算机能够支持多个程序并发执行的需求变得十分迫切。由此产生了进程的概念。进程在多数早期多任务操作 系统中是执行工作的基本单元。进程是包含程序指令和相关资源的集合。每个进程和其他进程一起参与调度,竞争CPU,内存等系统资源。
2、每次进程切换,都存在进程资源的保存和恢复动作,这称为上下文切换。 进程的引入可以解决支持多用户的问题,但是多进程系统也在如下方面产生了新的问题:进程频繁切换引起的额外开销可能会严重影响系统性能。 进程间通信要求复杂的系统级实现。 在程序功能日趋复杂的情况下,上述缺陷也就凸现出来。比如,一个简单的GUI程序,为了有更好的交互性,通常用一个任务支持界面交互,另一个任务支持后台运算。如果每个任务均由一个进程来实现,那会相当低效。对每个进程来说,系统资源看上去都是其独占的。比如内存空间,每个进程认为自己的内存空间是独有的。一次切换,这些独立资源都需要切换。 由此就演化出了利用分配给同一个进程的资源,尽
3、量实现多个任务的方法。这也就引入了线程的概念。同一个进程内部的多个线程,共享的是同一个进程的所有资源。比如,与每个进程独有自己的内存空间不同,同属一个进程的多个线程共享该进程的内存空间。例如在进程地址空间中有一个全局变量globalVar,若A线程将其赋值为1,则另一线程B可以看到该变量值为1。两个线程看到的全局变量globalVar是同一个变量。通过线程可以支持同一个应用程序内部的并发,免去了进程频繁切换的开销,另外并发任务间通信也更简单。 目前多线程应用主要用于两大领域:网络应用和嵌入式应用。为什么在这两个领域应用较多呢?因为多线程应用能够解决两大问题:并发。网络程序具有天生的并发性。比如
4、网络数据库可能需要同时处理数以千计的请求。而由于网络连接的时延不确定性和不可靠性,一旦等待一次网络交互,可以让当前线程进入睡眠,退出调度,处理其他线程。这样就能够有效利用系统资源,充分发挥系统 实时处理能力。线程的切换是轻量级的,所以可以保证足够快。每当有事件发生,状态改变,都能有线程及时响应,而且每次线程内部处理的计算强度和复杂度都不大。在这种情况下,多线程实现的模型也是高效的。在有些语言中,对多线程或者并发的支持是直接内建在语言中的,比如Ada和VHDL。在C+里面,对多线程的支持由具体操作系统提供的函数接口支持。不同的系统中具体实现方法不同。后面所有例子只给出windows和Unix/L
5、inux的实现。在后面的实现中,考虑的是尽量封装隔离底层的多线程函数接口,屏蔽操作系统底层的线程实现具体细节,介绍的重点是多线程编程中较通用的概念。同时也尽量体现C+面向对象的一面。最后,由于空闲时间有限,我只求示例代码能够明确表达自己的意思即可。至于代码的尽善尽美就只能有劳各位尽力以为之了。 第2节 线程的创建 本节介绍如下内容:线程状态、线程运行环境、线程类定义 示例程序:线程类的Windows和Unix实现 线程状态 在一个线程的生存期内,可以在多种状态之间转换。不同操作系统可以实现不同的线程模型,定义许多不同的线程状态,每个状态还可以包含多个子状态。但大体说来,如下几种状态是通用的:
6、就绪:参与调度,等待被执行。一旦被调度选中,立即开始执行。 运行:占用CPU,正在运行中。 休眠:暂不参与调度,等待特定事件发生。 中止:已经运行完毕,等待回收线程资源(要注意,这个很容易误解,后面解释)。 线程环境 线程存在于进程之中。进程内所有全局资源对于内部每个线程均是可见的。 进程内典型全局资源有如下几种: 代码区。这意味着当前进程空间内所有可见的函数代码,对于每个线程来说也是可见的。 静态存储区。全局变量。静态变量。 动态存储区。也就是堆空间。 线程内典型的局部资源有: 本地栈空间。存放本线程的函数调用栈,函数内部的局部变量等。 部分寄存器变量。例如本线程下一步要执行代码的指针偏移量
7、。 一个进程发起之后,会首先生成一个缺省的线程,通常称这个线程为主线程。C/C+程序中主线程就是通过main函数进入的线程。由主线程衍生的线程称为从线程,从线程也可以有自己的入口函数,作用相当于主线程的main函数。 这个函数由用户指定。Pthread和winapi中都是通过传入函数指针实现。在指定线程入口函数时,也可以指定入口函数的参数。就像main函数有固定的格式要求一样,线程的入口函数一般也有固定的格式要求,参数通常都是void *类型,返回类型在pthread中是void *, winapi中是unsigned int,而且都需要是全局函数。最常见的线程模型中,除主线程较为特殊之外,其
8、他线程一旦被创建,相互之间就是对等关系 (peer to peer), 不存在隐含的层次关系。每个进程可以创建的最大线程数由具体实现决定。 为了更好的理解上述概念,下面通过具体代码来详细说明。 线程类接口定义。一个线程类无论具体执行什么任务,其基本的共性无非就是: 创建并启动线程、停止线程。另外还有就是能睡,能等,能分离执行(有点拗口,后面再解释)。 还有其他的可以继续加 将线程的概念加以抽象,可以为其定义如下的类: 文件 thread.h #ifndef _THREAD_H_ #define _THREAD_H_ class Thread public: Thread(); virtual
9、Thread(); int start (void * = NULL); void stop(); void sleep (int); void detach(); void * wait(); protected: virtual void * run(void *) = 0; private: /这部分win和unix略有不同,先不定义,后面再分别实现。 /顺便提一下,我很不习惯写中文注释,这里为了更明白一 /点还是选用中文。 ; #endif Thread:start()函数是线程启动函数,其输入参数是无类型指针。 Thread:stop()函数中止当前线程。 Thread:sleep(
10、)函数让当前线程休眠给定时间,单位为秒。 Thread:run()函数是用于实现线程类的线程函数调用。 Thread:detach()和thread:wait()函数涉及的概念略复杂一些。在稍后再做解释。 Thread类是一个虚基类,派生类可以重载自己的线程函数。下面是一个例子。 示例程序 代码写的都不够精致,暴力类型转换比较多,欢迎有闲阶级美化,谢过了先。 文件create.h #ifndef _CREATOR_H_ #define _CREATOR_H_ #include #include thread.h class Create: public Thread protected: vo
11、id * run(void * param) char * msg = (char*) param; printf (%sn, msg); /sleep(100); 可以试着取消这行注释,看看结果有什么不同。 printf(One day past.n); return NULL; ; #endif 然后,实现一个main函数,来看看具体效果: 文件Genesis.cpp #include #include create.h int main(int argc, char* argv) Create monday; Create tuesday; printf(At the first God
12、 made the heaven and the earth.n); monday.start(Naming the light, Day, and the dark, Night, the first day.); tuesday.start(Gave the arch the name of Heaven, the second day.); printf(These are the generations of the heaven and the earth.n); return 0; 编译运行,程序输出如下: At the first God made the heaven and
13、the earth. These are the generations of the heaven and the earth. 令人惊奇的是,由周一和周二对象创建的子线程似乎并没有执行!这是为什么呢?别急,在最后的printf语句之前加上如下语句: monday.wait(); tuesday.wait(); 重新编译运行,新的输出如下: At the first God made the heaven and the earth. Naming the light, Day, and the dark, Night, the first day. One day past. Gave t
14、he arch the name of Heaven, the second day. One day past. These are the generations of the heaven and the earth. 为了说明这个问题,需要了解前面没有解释的Thread:detach()和Thread:wait()两个函数的含义。 无论在windows中,还是Posix中,主线程和子线程的默认关系是:无论子线程执行完毕与否,一旦主线程执行完毕退出,所有子线程执行都会终止。这时整个进程结束或僵死(部分线程保持一种终止执行但还未销毁的状态,而进程必须在其所有线程销毁后销毁,这时进程处于僵死
15、状态),在第一个例子的输出中,可以看到子线程还来不及执行完毕,主线程的main()函数就已经执行完毕,从而所有子线程终止。 需要强调的是,线程函数执行完毕退出,或以其他非常方式终止,线程进入终止态(请回顾上面说的线程状态),但千万要记住的是,进入终止态后,为线程分配的系统资源并不一定已经释放,而且可能在系统重启之前,一直都不能释放。终止态的线程,仍旧作为一个线程实体存在与操作系统中。(这点在win和unix中是一致的。)而什么时候销毁线程,取决于线程属性。通常,这种终止方式并非我们所期望的结果,而且一个潜在的问题是未执行完就终止的子线程,除了作为线程实体占用系统资源之外,其线程函数所拥有的资源
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 多线程编程入门 多线程 编程 入门
限制150内