C++多线程入门(一) 第1节 背景 为了更好的理解多线程的概念,先对进程,线程的概念背景做一下简单介绍。 早期的计算机系统都只允许一个程序独占系统资源,一次只能执行一个程序。在大型机年代,计算能力是一种宝贵资源。对于资源拥有方来说,最好的生财之道自然是将同一资源同时租售给尽可能多的用户。最理想的情况是垄断全球计算市场。所以不难理解为何当年IBM预测“全球只要有4台计算机就够了”。 这种背景下,一个计算机能够支持多个程序并发执行的需求变得十分迫切。由此产生了进程的概念。进程在多数早期多任务操作系统中是执行工作的基本单元。进程是包含程序指令和相关资源的集合。每个进程和其他进程一起参与调度,竞争CPU,内存等系统资源。每次进程切换,都存在进程资源的保存和恢复动作,这称为上下文切换。 进程的引入可以解决支持多用户的问题,但是多进程系统也在如下方面产生了新的问题: ? 进程频繁切换引起的额外开销可能会严重影响系统性能。 ? 进程间通信要求复杂的系统级实现。 在程序功能日趋复杂的情况下,上述缺陷也就凸现出来。比如,一个简单的GUI程序,为了有更好的交互性,通常用一个任务支持界面交互,另一个任务支持后台运算。如果每个任务均由一个进程来实现,那会相当低效。对每个进程来说,系统资源看上去都是其独占的。比如内存空间,每个进程认为自己的内存空间是独有的。一次切换,这些独立资源都需要切换。 由此就演化出了利用分配给同一个进程的资源,尽量实现多个任务的方法。这也就引入了线程的概念。同一个进程内部的多个线程,共享的是同一个进程的所有资源。 比如,与每个进程独有自己的内存空间不同,同属一个进程的多个线程共享该进程的内存空间。例如在进程地址空间中有一个全局变量globalVar,若A线程将其赋值为1,则另一线程B可以看到该变量值为1。两个线程看到的全局变量globalVar是同一个变量。 通过线程可以支持同一个应用程序内部的并发,免去了进程频繁切换的开销,另外并发任务间通信也更简单。 目前多线程应用主要用于两大领域:网络应用和嵌入式应用。为什么在这两个领域应用较多呢?因为多线程应用能够解决两大问题: ? 并发。网络程序具有天生的并发性。比如网络数据库可能需要同时处理数以千计的请求。而由于网络连接的时延不确定性和不可靠性,一旦等待一次网络交互,可以让当前线程进入睡眠,退出调度,处理其他线程。这样就能够有效利用系统资源,充分发挥系统处理能力。 ? 实时。线程的切换是轻量级的,所以可以保证足够快。每当有事件发生,状态改变,都能有线程及时响应,而且每次线程内部处理的计算强度和复杂度都不大。在这种情况下,多线程实现的模型也是高效的。 在有些语言中,对多线程或者并发的支持是直接内建在语言中的,比如Ada和VHDL。在C++里面,对多线程的支持由具体操作系统提供的函数接口支持。不同的系统中具体实现方法不同。后面所有例子只给出windows和Unix/Linux的实现。 在后面的实现中,考虑的是尽量封装隔离底层的多线程函数接口,屏蔽操作系统底层的线程实现具体细节,介绍的重点是多线程编程中较通用的概念。同时也尽量体现C++面向对象的一面。 最后,由于空闲时间有限,我只求示例代码能够明确表达自己的意思即可。至于代码的尽善尽美就只能有劳各位尽力以为之了。 ? 第2节 线程的创建 本节介绍如下内容 ? 线程状态 ? 线程运行环境 ? 线程类定义 ? 示例程序 ? 线程类的Windows和Unix实现 线程状态 在一个线程的生存期内,可以在多种状态之间转换。不同操作系统可以实现不同的线程模型,定义许多不同的线程状态,每个状态还可以包含多个子状态。但大体说来,如下几种状态是通用的: ? 就绪:参与调度,等待被执行。一旦被调度选中,立即开始执行。 ? 运行:占用CPU,正在运行中。 ? 休眠:暂不参与调度,等待特定事件发生。 ? 中止:已经运行完毕,等待回收线程资源(要注意,这个很容易误解,后面解释)。 线程环境 线程存在于进程之中。进程内所有全局资源对于内部每个线程均是可见的。 进程内典型全局资源有如下几种: ? 代码区。这意味着当前进程空间内所有可见的函数代码,对于每个线程来说也是可见的。 ? 静态存储区。全局变量。静态变量。 ? 动态存储区。也就是堆空间。 线程内典型的局部资源有: ? 本地栈空间。存放本线程的函数调用栈,函数内部的局部变量等。 ? 部分寄存器变量。例如本线程下一步要执行代码的指针偏移量。 一个进程发起之后,会首先生成一个缺省的线程,通常称这个线程为主线程。C/C++程序中主线程就是通过main函数进入的线程。由主线程衍生的线程称为从线程,从线程也可以有自己的入口函数,作用相当于主线程的main函数。 这个函数由用户指定。Pthread和winapi中都是通过传入函数指针实现。在指定线程入口函数时,也可以指定入口函数的参数。就像main函数有固定的格式要求一样,线程的入口函数一般也有固定的格式要求,参数通常都是void *类型,返回类型在pthread中是void *, winapi中是unsigned int,而且都需要是全局函数。 最常见的线程模型中,除主线程较为特殊之外,其他线程一旦被创建,相互之间就是对等关系 (peer to peer), 不存在隐含的层次关系。每个进程可以创建的最大线程数由具体实现决定。 为了更好的理解上述概念,下面通过具体代码来详细说明。 线程类接口定义 一个线程类无论具体执行什么任务,其基本的共性无非就是 ? 创建并启动线程 ? 停止线程 ? 另外还有就是能睡,能等,能分离执行(有点拗口,后面再解释)。 ? 还有其他的可以继续加… 将线程的概念加以抽象,可以为其定义如下的类: 文件 thread.h #ifndef __THREAD__H_ #define __THREAD__H_ class Thread { public: Thread(); virtual ~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()函数让当前线程休眠给定时间,单位为秒。 Thread::run()函数是用于实现线程类的线程函数调用。 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*) param; printf ("%s\n", 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.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 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 the 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中,主线程和子线程的默认关系是: 无论子线程执行完毕与否,一旦主线程执行完毕退出,所有子线程执行都会终止。这时整个进程结束或僵死(部分线程保持一种终止执行但还未销毁的状态,而进程必须在其所有线程销毁后销毁,这时进程处于僵死状态),在第一个例子的输出中,可以看到子线程还 阅读全文>> |
浙江在线健康网2月27日消息(通讯员 沈铁梅)</STRONG>早餐是一天中最重要的一餐,它提供的能量和营养素占了全天总需求的30%以上。而在日前公布的《中国7城市中小学饮食行为调查报告》显示,我国中小学生吃早餐的比率超过90%,但是大部分中小学生早餐营养不充分。“早餐吃饱却没吃好”,成了普遍问题。 人体经过一夜的酣睡,储备的能量已大量被消耗,激素分泌进入低谷,大脑和记忆机能组织急需补充营养。因此早餐是不是吃好直接影响了一天的工作和学习效率。最新的研究就证实了,吃好早餐有助于一整天的记忆,按时吃早餐的人思维更具创造性。 中老年人喜欢“路边摊”稀粥、油条、馒头但却营养单一;年轻一族钟情的蛋糕面包往往油脂含量高,且糖分多。那么怎样才是一顿营养丰富的早餐呢?北京大学人民医院内分泌科主任、美国康宝莱营养咨询委员会顾问纪立农教授将秘诀归纳为“三杯水”,教您轻松获取早餐七大营养。 早餐七大营养</STRONG> 专家认为,科学的早餐应是低热能、营养均衡的,碳水化合物、脂肪、蛋白质、维生素、矿物质和水一样都不能少,特别是要富含膳食纤维。科学的早餐应包括四种类别的食物,分别是以 |
护脑:菠菜</STRONG> 因拥有胡萝卜素以及超氧化物歧化酶等成分的“还原食物”,可以阻止脑血管的病变而保护大脑。哪些属于“还原食物”呢?专家研究认为,菠菜首当其冲。其次为韭菜、葱、豌豆角、西红柿、胡萝卜、小青菜、大豆、蒜叶等蔬菜,核桃、花生、开心果、腰果、松子、杏仁等壳类食物,以及糙米饭、猪肝汤等都值得你补脑时选用。 护眼:红薯</STRONG> 维生素A素有“护眼小卫士”之称,如果人体缺乏它,眼睛感受弱光的能力便会下降,对黑暗环境的适应能力也会减退,严重时容易患上夜盲症。维生素A是由胡萝卜素转变而成的。除胡萝卜之外,红薯中也富含丰富的胡萝卜素,能提供丰富的维生素A,可以增进视力,而且常食红薯对皮肤有好处。 护发:海带</STRONG> 说起护发的食物,可能你知道得很多,例如能令头发乌黑的黑芝麻,能令毛发生长的生姜,或者是能令头发闪亮的核桃等。但你知道食物护发的全能冠军是谁吗?它就是海带,营养专家认为,经常食用海带不但能补充身体的碘元素,而且对头发的生长、滋润、亮泽也都具有特殊 |
© 2024 Jinbay.com All rights reserved.
版权所有金海湾。 未经许可,不得转载。