C多线程编程入门.docx
《C多线程编程入门.docx》由会员分享,可在线阅读,更多相关《C多线程编程入门.docx(14页珍藏版)》请在三一办公上搜索。
1、C 多线程编程入门第1节 背景 为了更好的理解多线程的概念,先对进程,线程的概念背景做一下简单介绍。早期的计算机系统都只允许一个程序独占系统资源,一次只能执行一个程序。在大型机年代,计算能力是一种宝贵资源。对于资源拥有方来说,最好的生财之道自然是将同一资源同时租售给尽可能多的用户。最理想的情况是垄断全球计算市场。所以不难理 解为何当年IBM预测“全球只要有4台计算机就够了”。 这种背景下,一个计算机能够支持多个程序并发执行的需求变得十分迫切。由此产生了进程的概念。进程在多数早期多任务操作 系统中是执行工作的基本单元。进程是包含程序指令和相关资源的集合。每个进程和其他进程一起参与调度,竞争CPU
2、,内存等系统资源。每次进程切换,都存在进程资源的保存和恢复动作,这称为上下文切换。 进程的引入可以解决支持多用户的问题,但是多进程系统也在如下方面产生了新的问题:进程频繁切换引起的额外开销可能会严重影响系统性能。 进程间通信要求复杂的系统级实现。 在程序功能日趋复杂的情况下,上述缺陷也就凸现出来。比如,一个简单的GUI程序,为了有更好的交互性,通常用一个任务支持界面交互,另一个任务支持后台运算。如果每个任务均由一个进程来实现,那会相当低效。对每个进程来说,系统资源看上去都是其独占的。比如内存空间,每个进程认为自己的内存空间是独有的。一次切换,这些独立资源都需要切换。 由此就演化出了利用分配给同
3、一个进程的资源,尽量实现多个任务的方法。这也就引入了线程的概念。同一个进程内部的多个线程,共享的是同一个进程的所有资源。比如,与每个进程独有自己的内存空间不同,同属一个进程的多个线程共享该进程的内存空间。例如在进程地址空间中有一个全局变量globalVar,若A线程将其赋值为1,则另一线程B可以看到该变量值为1。两个线程看到的全局变量globalVar是同一个变量。通过线程可以支持同一个应用程序内部的并发,免去了进程频繁切换的开销,另外并发任务间通信也更简单。 目前多线程应用主要用于两大领域:网络应用和嵌入式应用。为什么在这两个领域应用较多呢?因为多线程应用能够解决两大问题:并发。网络程序具有
4、天生的并发性。比如网络数据库可能需要同时处理数以千计的请求。而由于网络连接的时延不确定性和不可靠性,一旦等待一次网络交互,可以让当前线程进入睡眠,退出调度,处理其他线程。这样就能够有效利用系统资源,充分发挥系统 实时处理能力。线程的切换是轻量级的,所以可以保证足够快。每当有事件发生,状态改变,都能有线程及时响应,而且每次线程内部处理的计算强度和复杂度都不大。在这种情况下,多线程实现的模型也是高效的。在有些语言中,对多线程或者并发的支持是直接内建在语言中的,比如Ada和VHDL。在C+里面,对多线程的支持由具体操作系统提供的函数接口支持。不同的系统中具体实现方法不同。后面所有例子只给出windo
5、ws和Unix/Linux的实现。在后面的实现中,考虑的是尽量封装隔离底层的多线程函数接口,屏蔽操作系统底层的线程实现具体细节,介绍的重点是多线程编程中较通用的概念。同时也尽量体现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 Thread; int start (void
9、 * = 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函数让当前线程休眠给定时间,单位为秒。 Thread:run函数是用于实
10、现线程类的线程函数调用。 Thread:detach和thread:wait函数涉及的概念略复杂一些。在稍后再做解释。 Thread类是一个虚基类,派生类可以重载自己的线程函数。下面是一个例子。 示例程序 代码写的都不够精致,暴力类型转换比较多,欢迎有闲阶级美化,谢过了先。 文件create.h #ifndef _CREATOR_H_ #define _CREATOR_H_ #include #include thread.h class Create: public Thread protected: void * run(void * param) char * msg = (char*)
11、 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 made the heaven and the earth.n); monday.s
12、tart(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 the earth. These are the generations of the
13、 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 the arch the name of Heaven, the second day. One
14、 day past. These are the generations of the heaven and the earth. 为了说明这个问题,需要了解前面没有解释的Thread:detach和Thread:wait两个函数的含义。 无论在windows中,还是Posix中,主线程和子线程的默认关系是:无论子线程执行完毕与否,一旦主线程执行完毕退出,所有子线程执行都会终止。这时整个进程结束或僵死,在第一个例子的输出中,可以看到子线程还来不及执行完毕,主线程的main函数就已经执行完毕,从而所有子线程终止。 需要强调的是,线程函数执行完毕退出,或以其他非常方式终止,线程进入终止态,但千万要
15、记住的是,进入终止态后,为线程分配的系统资源并不一定已经释放,而且可能在系统重启之前,一直都不能释放。终止态的线程,仍旧作为一个线程实体存在与操作系统中。而什么时候销毁线程,取决于线程属性。通常,这种终止方式并非我们所期望的结果,而且一个潜在的问题是未执行完就终止的子线程,除了作为线程实体占用系统资源之外,其线程函数所拥有的资源也不一定能释放。所以,针对这个问题,主线程和子线程之间通常定义两种关系: 可会合(joinable)。这种关系下,主线程需要明确执行等待操作。在子线程结束后,主线程的等待操作执行完毕,子线程和主线程会合。这时主线程继续执行等待操作之后的下一步操作。主线程必须会合可会合的
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 多线程编程入门 多线程 编程 入门
链接地址:https://www.31ppt.com/p-3060483.html