工程计价总说明怎么写 C++ 写底层,但是底层是什么,C++ 怎么写?

[转载]总结: C++ 中如何把输出结果写入到文件中
已有 13543 次阅读
|个人分类:|系统分类:|文章来源:转载
本文是我在网上搜到额一些经验汇总。文件 I/O 在C++中比烤蛋糕简单多了。 在这篇文章里,我会详细解释ASCII和二进制文件的输入输出的每个细节,值得注意的是,所有这些都是用C++完成的。  一、ASCII 输出  为了使用下面的方法, 你必须包含头文件&fstream.h&(译者注:在标准C++中,已经使用&fstream&取代& fstream.h&,所有的C++标准头文件都是无后缀的。)。这是 &iostream.h&的一个扩展集, 提供有缓冲的文件输入输出操作. 事实上, &iostream.h& 已经被&fstream.h&包含了, 所以你不必包含所有这两个文件, 如果你想显式包含他们,那随便你。我们从文件操作类的设计开始, 我会讲解如何进行ASCII I/O操作。如果你猜是&fstream,& 恭喜你答对了! 但这篇文章介绍的方法,我们分别使用&ifstream&?和 &ofstream& 来作输入输出。  如果你用过标准控制台流&cin&?和 &cout,& 那现在的事情对你来说很简单。 我们现在开始讲输出部分,首先声明一个类对象。  这就可以了,不过你要打开一个文件的话, 必须像这样调用ofstream::open()。fout.open(&output.txt&);  你也可以把文件名作为构造参数来打开一个文件.ofstream fout(&output.txt&);  这是我们使用的方法, 因为这样创建和打开一个文件看起来更简单. 顺便说一句, 如果你要打开的文件不存在,它会为你创建一个, 所以不用担心文件创建的问题. 现在就输出到文件,看起来和&cout&的操作很像。 对不了解控制台输出&cout&的人, 这里有个例子。int num = 150;char name[] = &John Doe&;fout && &Here is a number: & && num && &n&;fout && &Now here is a string: & && name && &n&;  现在保存文件,你必须关闭文件,或者回写文件缓冲. 文件关闭之后就不能再操作了, 所以只有在你不再操作这个文件的时候才调用它,它会自动保存文件。 回写缓冲区会在保持文件打开的情况下保存文件, 所以只要有必要就使用它。回写看起来像另一次输出, 然后调用方法关闭。像这样:fout && fout.close();   现在你用文本编辑器打开文件,内容看起来是这样:  Here is a number: 150 Now here is a string: John Doe  很简单吧! 现在继续文件输入, 需要一点技巧, 所以先确认你已经明白了流操作,对 &&&& 和&&&& 比较熟悉了, 因为你接下来还要用到他们。继续…  二、ASCII 输入  输入和&cin& 流很像. 和刚刚讨论的输出流很像, 但你要考虑几件事情。在我们开始复杂的内容之前, 先看一个文本:  12 GameDev 15.45 L This is really awesome!  为了打开这个文件,你必须创建一个in-stream对象,?像这样。ifstream fin(&input.txt&);  现在读入前四行. 你还记得怎么用&&&& 操作符往流里插入变量和符号吧?好,?在 &&&& (插入)?操作符之后,是&&&& (提取) 操作符. 使用方法是一样的. 看这个代码片段.char letter, word[8];fin && fin && fin && fin &&  也可以把这四行读取文件的代码写为更简单的一行。fin && number && word && real &&  它是如何运作的呢? 文件的每个空白之后, &&&& 操作符会停止读取内容, 直到遇到另一个&&操作符. 因为我们读取的每一行都被换行符分割开(是空白字符), &&&& 操作符只把这一行的内容读入变量。这就是这个代码也能正常工作的原因。但是,可别忘了文件的最后一行。  This is really awesome!  如果你想把整行读入一个char数组, 我们没办法用&&&&?操作符,因为每个单词之间的空格(空白字符)会中止文件的读取。为了验证:char sentence[101]; fin &&  我们想包含整个句子, &This is really awesome!& 但是因为空白, 现在它只包含了&This&. 很明显, 肯定有读取整行的方法, 它就是getline()。这就是我们要做的。fin.getline(sentence, 100);  这是函数参数. 第一个参数显然是用来接受的char数组. 第二个参数是在遇到换行符之前,数组允许接受的最大元素数量. 现在我们得到了想要的结果:“This is really awesome!”。  你应该已经知道如何读取和写入ASCII文件了。但我们还不能罢休,因为二进制文件还在等着我们。  三、二进制 输入输出  二进制文件会复杂一点, 但还是很简单的。首先你要注意我们不再使用插入和提取操作符(译者注:&& 和 && 操作符). 你可以这么做,但它不会用二进制方式读写。你必须使用read() 和write() 方法读取和写入二进制文件. 创建一个二进制文件, 看下一行。ofstream fout(&file.dat&, ios::binary);  这会以二进制方式打开文件, 而不是默认的ASCII模式。首先从写入文件开始。函数write() 有两个参数。 第一个是指向对象的char类型的指针, 第二个是对象的大小(译者注:字节数)。 为了说明,看例子。int number = 30; fout.write((char *)(&number), sizeof(number));  第一个参数写做&(char *)(&number)&. 这是把一个整型变量转为char *指针。如果你不理解,可以立刻翻阅C++的书籍,如果有必要的话。第二个参数写作&sizeof(number)&. sizeof() 返回对象大小的字节数. 就是这样!  二进制文件最好的地方是可以在一行把一个结构写入文件。 如果说,你的结构有12个不同的成员。 用ASCII?文件,你不得不每次一条的写入所有成员。 但二进制文件替你做好了。 看这个。struct OBJECT { }obj.number = 15;obj.letter = ‘M’;fout.write((char *)(&obj), sizeof(obj));  这样就写入了整个结构! 接下来是输入. 输入也很简单,因为read()?函数的参数和 write()是完全一样的, 使用方法也相同。ifstream fin(&file.dat&, ios::binary); fin.read((char *)(&obj), sizeof(obj));  我不多解释用法, 因为它和write()是完全相同的。二进制文件比ASCII文件简单, 但有个缺点是无法用文本编辑器编辑。 接着, 我解释一下ifstream 和ofstream 对象的其他一些方法作为结束.  四、更多方法  我已经解释了ASCII文件和二进制文件, 这里是一些没有提及的底层方法。  检查文件  你已经学会了open() 和close() 方法, 不过这里还有其它你可能用到的方法。  方法good() 返回一个布尔值,表示文件打开是否正确。  类似的,bad() 返回一个布尔值表示文件打开是否错误。 如果出错,就不要继续进一步的操作了。  最后一个检查的方法是fail(), 和bad()有点相似, 但没那么严重。  读文件  方法get() 每次返回一个字符。  方法ignore(int,char) 跳过一定数量的某个字符, 但你必须传给它两个参数。第一个是需要跳过的字符数。 第二个是一个字符, 当遇到的时候就会停止。 例子,fin.ignore(100, ‘n’);  会跳过100个字符,或者不足100的时候,跳过所有之前的字符,包括 ‘n’。  方法peek() 返回文件中的下一个字符, 但并不实际读取它。所以如果你用peek() 查看下一个字符, 用get() 在peek()之后读取,会得到同一个字符, 然后移动文件计数器。  方法putback(char) 输入字符, 一次一个, 到流中。我没有见到过它的使用,但这个函数确实存在。  写文件  只有一个你可能会关注的方法.?那就是 put(char), 它每次向输出流中写入一个字符。  打开文件  当我们用这样的语法打开二进制文件:ofstream fout(&file.dat&, ios::binary);  &ios::binary&是你提供的打开选项的额外标志. 默认的, 文件以ASCII方式打开, 不存在则创建, 存在就覆盖. 这里有些额外的标志用来改变选项。  ios::app 添加到文件尾  ios::ate 把文件标志放在末尾而非起始。  ios::trunc 默认. 截断并覆写文件。  ios::nocreate 文件不存在也不创建。  ios::noreplace 文件存在则失败。  文件状态  我用过的唯一一个状态函数是eof(), 它返回是否标志已经到了文件末尾。 我主要用在循环中。 例如, 这个代码断统计小写‘e’ 在文件中出现的次数。ifstream fin(&file.txt&);while (!fin.eof()) {ch = fin.get();if (ch == ‘e’) counter++;}fin.close();  我从未用过这里没有提到的其他方法。 还有很多方法,但是他们很少被使用。参考C++书籍或者文件流的帮助文档来了解其他的方法。  结论  你应该已经掌握了如何使用ASCII文件和二进制文件。有很多方法可以帮你实现输入输出,尽管很少有人使用他们。我知道很多人不熟悉文件I/O操作,我希望这篇文章对你有所帮助。 每个人都应该知道. 文件I/O还有很多显而易见的方法,?例如包含文件 &stdio.h&. 我更喜欢用流是因为他们更简单。 祝所有读了这篇文章的人好运, 也许以后我还会为你们写些东西。在看C++编程思想中,每个练习基本都是使用ofstream,ifstream,fstream,以前粗略知道其用法和含义,在看了几位大牛的博文后,进行整理和总结:这里主要是讨论fstream的内容:[java]&1.打开文件在fstream类中,成员函数open()实现打开文件的操作,从而将数据流和文件进行关联,通过ofstream,ifstream,fstream对象进行对文件的读写操作函数:open()[cpp]&参数: filename & 操作文件名 & & & & & mode & & & &打开文件的方式 & & & & & prot & & & & 打开文件的属性 & & & & & & & & & & & & & &//基本很少用到,在查看资料时,发现有两种方式打开文件的方式在ios类(所以流式I/O的基类)中定义,有如下几种方式:ios::in为输入(读)而打开文件ios::out为输出(写)而打开文件ios::ate初始位置:文件尾ios::app所有输出附加在文件末尾ios::trunc如果文件已存在则先删除该文件ios::binary二进制方式这些方式是能够进行组合使用的,以“或”运算(“|”)的方式:例如[cpp]&打开文件的属性同样在ios类中也有定义:0普通文件,打开操作1只读文件2隐含文件4系统文件对于文件的属性也可以使用“或”运算和“+”进行组合使用,这里就不做说明了。很多程序中,可能会碰到ofstream out(&Hello.txt&), ifstream in(&...&),fstream foi(&...&)这样的的使用,并没有显式的去调用open()函数就进行文件的操作,直接调用了其默认的打开方式,因为在stream类的构造函数中调用了open()函数,并拥有同样的构造函数,所以在这里可以直接使用流对象进行文件的操作,默认方式如下:[cpp]&当使用默认方式进行对文件的操作时,你可以使用成员函数is_open()对文件是否打开进行验证2.关闭文件当文件读写操作完成之后,我们必须将文件关闭以使文件重新变为可访问的。成员函数close(),它负责将缓存中的数据排放出来并关闭文件。这个函数一旦被调用,原先的流对象就可以被用来打开其它的文件了,这个文件也就可以重新被其它的进程所访问了。为防止流对象被销毁时还联系着打开的文件,析构函数将会自动调用关闭函数close。3.文本文件的读写类ofstream, ifstream 和fstream 是分别从ostream, istream 和iostream 中引申而来的。这就是为什么 fstream 的对象可以使用其父类的成员来访问数据。一般来说,我们将使用这些类与同控制台(console)交互同样的成员函数(cin 和 cout)来进行输入输出。如下面的例题所示,我们使用重载的插入操作符&&:[cpp]&从文件中读入数据也可以用与 cin&&的使用同样的方法:[cpp]&上面的例子读入一个文本文件的内容,然后将它打印到屏幕上。注意我们使用了一个新的成员函数叫做eof ,它是ifstream 从类 ios 中继承过来的,当到达文件末尾时返回true 。状态标志符的验证(Verification of state flags)除了eof()以外,还有一些验证流的状态的成员函数(所有都返回bool型返回值):bad()如果在读写过程中出错,返回 true 。例如:当我们要对一个不是打开为写状态的文件进行写入时,或者我们要写入的设备没有剩余空间的时候。fail()除了与bad() 同样的情况下会返回 true 以外,加上格式错误时也返回true ,例如当想要读入一个整数,而获得了一个字母的时候。eof()如果读文件到达文件末尾,返回true。good()这是最通用的:如果调用以上任何一个函数返回true 的话,此函数返回 false 。要想重置以上成员函数所检查的状态标志,你可以使用成员函数clear(),没有参数。获得和设置流指针(get and put stream pointers)所有输入/输出流对象(i/o streams objects)都有至少一个流指针:ifstream, 类似istream, 有一个被称为get pointer的指针,指向下一个将被读取的元素。ofstream, 类似 ostream, 有一个指针 put pointer ,指向写入下一个元素的位置。fstream, 类似 iostream, 同时继承了get 和 put我们可以通过使用以下成员函数来读出或配置这些指向流中读写位置的流指针:tellg() 和 tellp()这两个成员函数不用传入参数,返回pos_type 类型的值(根据ANSI-C++ 标准) ,就是一个整数,代表当前get 流指针的位置 (用tellg) 或 put 流指针的位置(用tellp).seekg() 和seekp()这对函数分别用来改变流指针get 和put的位置。两个函数都被重载为两种不同的原型:seekg ( pos_type position );seekp ( pos_type position );使用这个原型,流指针被改变为指向从文件开始计算的一个绝对位置。要求传入的参数类型与函数 tellg 和tellp 的返回值类型相同。seekg ( off_type offset, seekdir direction );seekp ( off_type offset, seekdir direction );使用这个原型可以指定由参数direction决定的一个具体的指针开始计算的一个位移(offset)。它可以是:ios::beg从流开始位置计算的位移ios::cur从流指针当前位置开始计算的位移ios::end从流末尾处开始计算的位移流指针 get 和 put 的值对文本文件(text file)和二进制文件(binary file)的计算方法都是不同的,因为文本模式的文件中某些特殊字符可能被修改。由于这个原因,建议对以文本文件模式打开的文件总是使用seekg 和 seekp的第一种原型,而且不要对tellg 或 tellp 的返回值进行修改。对二进制文件,你可以任意使用这些函数,应该不会有任何意外的行为产生。以下例子使用这些函数来获得一个二进制文件的大小: [cpp]&4.二进制文件在二进制文件中,使用&& 和&&,以及函数(如getline)来操作符输入和输出数据,没有什么实际意义,虽然它们是符合语法的。文件流包括两个为顺序读写数据特殊设计的成员函数:write 和 read。第一个函数 (write) 是ostream 的一个成员函数,都是被ofstream所继承。而read 是istream 的一个成员函数,被ifstream 所继承。类 fstream 的对象同时拥有这两个函数。它们的原型是:write ( char * buffer, streamsize size );read ( char * buffer, streamsize size );这里 buffer 是一块内存的地址,用来存储或读出数据。参数size 是一个整数值,表示要从缓存(buffer)中读出或写入的字符数。[cpp]&5.缓存和同步(Buffers and Synchronization)当我们对文件流进行操作的时候,它们与一个streambuf 类型的缓存(buffer)联系在一起。这个缓存(buffer)实际是一块内存空间,作为流(stream)和物理文件的媒介。例如,对于一个输出流, 每次成员函数put (写一个单个字符)被调用,这个字符不是直接被写入该输出流所对应的物理文件中的,而是首先被插入到该流的缓存(buffer)中。当缓存被排放出来(flush)时,它里面的所有数据或者被写入物理媒质中(如果是一个输出流的话),或者简单的被抹掉(如果是一个输入流的话)。这个过程称为同步(synchronization),它会在以下任一情况下发生:当文件被关闭时: 在文件被关闭之前,所有还没有被完全写出或读取的缓存都将被同步。当缓存buffer 满时:缓存Buffers 有一定的空间限制。当缓存满时,它会被自动同步。控制符明确指明:当遇到流中某些特定的控制符时,同步会发生。这些控制符包括:flush 和endl。明确调用函数sync(): 调用成员函数sync() (无参数)可以引发立即同步。这个函数返回一个int 值,等于-1 表示流没有联系的缓存或操作失败。
转载本文请联系原作者获取授权,同时请注明本文来自陈智罡科学网博客。链接地址:
上一篇:下一篇:
当前推荐数:0
评论 ( 个评论)
扫一扫,分享此博文
作者的精选博文
作者的其他最新博文
热门博文导读
Powered by
Copyright &相关文章推荐
#include // MFC Windows Forms support
using namespac...
MFC是微软提供的一个C++ UI类库,我觉得它最大的一个好处是提供了一些控件给你用,使你可以方便的弄个漂亮的UI界面出来.而UI这玩艺可重要了啊,用户觉得一个软件产品好不好用,实际上很大程度是看UI...
MFC是微软提供的一个C++ UI类库,我觉得它最大的一个好处是提供了一些控件给你用,使你可以方便的弄个漂亮的UI界面出来.而UI这玩艺可重要了啊,用户觉得一个软件产品好不好用,实际上很大程度是看UI...
MFC是在API的基础上封装出来的一个类库,给C++程序员在Windows上快速开发用的。 .Net类库是在Windows非托管API的基础上封装出来的一个托管类库,给程序员在各种Win...
C++:最强大的 .NET Framework编程语言
本文涉及:Microsoft Visual C++ 2005Microsoft Visual C++ .NETMicrosoft Visual...
C++ C# VC VC.net以及VC++有什么区别和联系?( 09:27:56)转载
C/C++是编程语言,C是C++的爸爸,也就是说C++从C发展而来,而C++完全兼容...
Visual C++/MFC入门教程
VC开发指南
  讲述Visual C++/MFC开发的基本知识,文档/视结构,窗体控件的使用和一些基本的网络开发知识。同时指出一些在开发...
转载:/jackie_howe/blog/static//
C语言是一种古老而又经久不衰的计算机程序设计语言,大...
MFC绘图类包括绘图设备类和绘图对象类
1 绘图设备类
CDC类-父类是CObject,封装的是一般的绘图设备,例如:显示器,
打印机等。
他的最新文章
讲师:Array
讲师:李志伟
他的热门文章
您举报文章:
举报原因:
原文地址:
原因补充:
(最多只允许输入30个字)其他回答(7)
VS2010当然可以写C的,TC是我刚学C时用的,太老了,至于C和C++哪个有前途,这个就根据你的方向了,假如你是做底层开发,肯定是C的本领越强越好,不过我个人认为C++是对c的升华,假如C学的不错了,肯定是有必要好好学习下c++的。再说现在搞IT的,很难说只学某一个编程语言就能打遍天下的,都是需要很多编程知识的,搞IT的,其它编程语言和技术,可能不需要个个精通,但一定要至少知道个大概,这样的程序猿才可能成为大牛。
觉得很多地方提到语言的时候写作c/c++是有原因的。可能会有人严格地说C和C++怎么怎么差异,但是实际使用中很少见只会C不会C++的,毕竟C++从语言上库上都是很好的扩展。
楼上说VS对C的支持我不是很清楚,但是VS对C++11的支持还是不错的。而且C++11的编译器据说集成到了MSBUILD。
学习C++还是建议看看刘未鹏的你应当如何学习C++(以及编程),其中提到的几本书真的很不错。虽然我没成为C++老鸟但全部原因在我。这篇博客真的指明了一条大路。
如果你不是老手不是已经到了VC限于平台或者版权。我建议你可以考虑用VC,很简单下载安装就可以跑了。资料很多。毕竟如果真的纠结工具恐怕你一两个月都开始不了,好工具太多了,gcc,llvm等。你真要做个5个文件的工程,你要不要学make?make之后要不要automake等等。你有听说了要用源码版本控制,要有不要折腾GIT,github速度慢是不是又要选择其他方案。等等,多年经验告诉我,磨刀不误砍柴工,不是任何阶段都适用。
前两天就跟其他人说了,好的语言好的框架,对于你的职业生涯可能连锦上添花都算不上。特别是开始期间,选择一个主流(c/c++,java,c#等)等掌握之后你就自然而然的明白这个语言优缺点,这个语言的前景,甚至于行业的前景。但如果你真的纠结于语言的选择,可能半年后发现自己还在原地打转。这个是过来人的经验。
VS2010能写C;
TC已经是过时的工具了;
C,C++都有前途;
驱动,底层用C。
首先我要纠正几个问题
1. VC和TC只是开发的IDE,只是简化开发提高工作效率的工具而已
2.C/C++的前途不是在于语言,而在于你能将其中任何语言学习和运用到那种程度
关于你的问题:
1. VS2010默认C++开发,但是同样可以进行C开发的,对驱动开发更是没有问题,只是在驱动开发中需要配置,做驱动开发相信你开始已经对这块有了解了
2.驱动开发一般来说都是推荐C来开发的,但是C++也是可以的,还是要看你自己喜欢那种语言了,我早是用C后WDM后采用C++了,但是还是喜欢用C来开发,只是需要注意下,在网上可以找到很多文章关于C++开发驱动需要注意的事项的
最后驱动开发最好先学习些调试技术,还有选择一个方便本机调试的虚拟机
写底层的C,一般不用vs这样的工具。你看看硬件研发的人就知道了,你开发产品之前,你需要选型,买指定的芯片,然后这些芯片都是有C的IDE。不同的芯片,IDE也会不同的。
VC和TC只是集成环境,是工具。C++是开发语言。
北京鼎普科技股份有限公司诚聘windows内核开发工程师、C/C++中高级开发人员、linux开发工程师、嵌入式开发工程师、架构师 QQ:(请把简历直接发到QQ邮箱中,谢谢!)
&&&您需要以后才能回答,未注册用户请先。使用 C++11 编写 Linux 多线程程序
前言在这个多核时代,如何充分利用每个 CPU 内核是一个绕不开的话题,从需要为成千上万的用户同时提供服务的服务端应用程序,到需要同时打开十几个页面,每个页面都有几十上百个链接的 web 浏览器应用程序,从保持着几 t 甚或几 p 的数据的数据库系统,到手机上的一个有良好用户响应能力的 app,为了充分利用每个 CPU 内核,都会想到是否可以使用多线程技术。这里所说的“充分利用”包含了两个层面的意思,一个是使用到所有的内核,再一个是内核不空闲,不让某个内核长时间处于空闲状态。在 C++98 的时代,C++标准并没有包含多线程的支持,人们只能直接调用操作系统提供的 SDK API 来编写多线程程序,不同的操作系统提供的 SDK API 以及线程控制能力不尽相同,到了 C++11,终于在标准之中加入了正式的多线程的支持,从而我们可以使用标准形式的类来创建与执行线程,也使得我们可以使用标准形式的锁、原子操作、线程本地存储 (TLS) 等来进行复杂的各种模式的多线程编程,而且,C++11 还提供了一些高级概念,比如 promise/future,packaged_task,async 等以简化某些模式的多线程编程。多线程可以让我们的应用程序拥有更加出色的性能,同时,如果没有用好,多线程又是比较容易出错的且难以查找错误所在,甚至可以让人们觉得自己陷进了泥潭,希望本文能够帮助您更好地使用 C++11 来进行 Linux 下的多线程编程。认识多线程首先我们应该正确地认识线程。维基百科对线程的定义是:线程是一个编排好的指令序列,这个指令序列(线程)可以和其它的指令序列(线程)并行执行,操作系统调度器将线程作为最小的 CPU 调度单元。在进行架构设计时,我们应该多从操作系统线程调度的角度去考虑应用程序的线程安排,而不仅仅是代码。当只有一个 CPU 内核可供调度时,多个线程的运行示意如下:图 1、单个 CPU 内核上的多个线程运行示意图我们可以看到,这时的多线程本质上是单个 CPU 的时间分片,一个时间片运行一个线程的代码,它可以支持并发处理,但是不能说是真正的并行计算。当有多个 CPU 或者多个内核可供调度时,可以做到真正的并行计算,多个线程的运行示意如下:图 2、双核 CPU 上的多个线程运行示意图从上述两图,我们可以直接得到使用多线程的一些常见场景:
进程中的某个线程执行了一个阻塞操作时,其它线程可以依然运行,比如,等待用户输入或者等待网络数据包的时候处理启动后台线程处理业务,或者在一个游戏引擎中,一个线程等待用户的交互动作输入,另外一个线程在后台合成下一帧要画的图像或者播放背景音乐等。
将某个任务分解为小的可以并行进行的子任务,让这些子任务在不同的 CPU 或者内核上同时进行计算,然后汇总结果,比如归并排序,或者分段查找,这样子来提高任务的执行速度。需要注意一点,因为单个 CPU 内核下多个线程并不是真正的并行,有些问题,比如 CPU 缓存不一致问题,不一定能表现出来,一旦这些代码被放到了多核或者多 CPU 的环境运行,就很可能会出现“在开发测试环境一切没有问题,到了实施现场就莫名其妙”的情况,所以,在进行多线程开发时,开发与测试环境应该是多核或者多 CPU 的,以避免出现这类情况。C++11 的线程类 std::threadC++11 的标准类 std::thread 对线程进行了封装,它的声明放在头文件 thread 中,其中声明了线程类 thread, 线程标识符 id,以及名字空间 this_thread,按照 C++11 规范,这个头文件至少应该兼容如下内容:清单 1.例子 thread 头文件主要内容namespace std{
struct thread{
// native_handle_type 是连接 thread 类和操作系统 SDK API 之间的桥梁。
typedef implementation-dependent native_handle_
native_handle_type native_handle();
struct id{
// 可以由==, & 两个运算衍生出其它大小关系运算。
bool operator==(thread::id x, thread::id y)
bool operator&(thread::id x, thread::id y)
template&class charT, class traits&
basic_ostream&charT, traits&&
operator&&(basic_ostream&charT, traits&&out, thread::id id);
// 哈希函数
template &class T&
template && struct hash&thread::id&;
id get_id()
// 构造与析构
template&class F, class… Args& explicit thread(F&f, Args&&… args);
~thread();
thread(const thread&) =
thread(thread&&)
thread& operator=( const thread&) =
thread& operator=(thread&&)
void swap(thread&)
bool joinable()
void join();
void detach();
// 获取物理线程数目
static unsigned hardware_concurrency()
namespace this_thead{
thread::id get_id();
void yield();
template&class Clock, class Duration&
void sleep_until(const chrono::time_point&Clock, Duration&& abs_time);
template&class Rep, class Period&
void sleep_for(const chromo::duration&Rep, Period&& rel_time);
}和有些语言中定义的线程不同,C++11 所定义的线程是和操作系的线程是一一对应的,也就是说我们生成的线程都是直接接受操作系统的调度的,通过操作系统的相关命令(比如 ps -M 命令)是可以看到的,一个进程所能创建的线程数目以及一个操作系统所能创建的总的线程数目等都由运行时操作系统限定。native_handle_type 是连接 thread 类和操作系统 SDK API 之间的桥梁,在 g++(libstdc++) for Linux 里面,native_handle_type 其实就是 pthread 里面的 pthread_t 类型,当 thread 类的功能不能满足我们的要求的时候(比如改变某个线程的优先级),可以通过 thread 类实例的 native_handle() 返回值作为参数来调用相关的 pthread 函数达到目的。thread::id 定义了在运行时操作系统内唯一能够标识该线程的标识符,同时其值还能指示所标识的线程的状态,其默认值 (thread::id()) 表示不存在可控的正在执行的线程(即空线程,比如,调用 thead() 生成的没有指定入口函数的线程类实例),当一个线程类实例的 get_id() 等于默认值的时候,即 get_id() == thread::id(),表示这个线程类实例处于下述状态之一:
尚未指定运行的任务
线程运行完毕
线程已经被转移 (move) 到另外一个线程类实例
线程已经被分离 (detached)空线程 id 字符串表示形式依具体实现而定,有些编译器为 0x0,有些为一句语义解释。有时候我们需要在线程执行代码里面对当前调用者线程进行操作,针对这种情况,C++11 里面专门定义了一个名字空间 this_thread,其中包括 get_id() 函数可用来获取当前调用者线程的 id,yield() 函数可以用来将调用者线程跳出运行状态,重新交给操作系统进行调度,sleep_until 和 sleep_for 函数则可以让调用者线程休眠若干时间。get_id() 函数实际上是通过调用 pthread_self() 函数获得调用者线程的标识符,而 yield() 函数则是通过调用操作系统 API sched_yield() 进行调度切换。如何创建和结束一个线程和 pthread_create 不同,使用 thread 类创建线程可以使用一个函数作为入口,也可以是其它的 Callable 对象,而且,可以给入口传入任意个数任意类型的参数:清单 2.例子 thread_run_func_var_args.ccint funcReturnInt(const char* fmt, ...){
va_start(ap, fmt);
vprintf( fmt, ap );
va_end(ap);
void threadRunFunction(void){
thread* t = new thread(funcReturnInt, "%d%s\n", 100, "\%");
t-&join();
我们也可以传入一个 Lambda 表达式作为入口,比如:清单 3.例子 thread_run_lambda.ccvoid threadRunLambda(void){
int a = 100,
thread* t = new thread( [](int ia, int ib){
cout && (ia + ib) &&
t-&join();
}一个类的成员函数也可以作为线程入口:清单 4.例子 thread_run_member_func.ccstruct God{
void create(const char* anything){
cout && "create " && anything &&
void threadRunMemberFunction(void){
thread* t = new thread( &God::create, god, "the world" );
t-&join();
}虽然 thread 类的初始化可以提供这么丰富和方便的形式,其实现的底层依然是创建一个 pthread 线程并运行之,有些实现甚至是直接调用 pthread_create 来创建。创建一个线程之后,我们还需要考虑一个问题:该如何处理这个线程的结束?一种方式是等待这个线程结束,在一个合适的地方调用 thread 实例的 join() 方法,调用者线程将会一直等待着目标线程的结束,当目标线程结束之后调用者线程继续运行;另一个方式是将这个线程分离,由其自己结束,通过调用 thread 实例的 detach() 方法将目标线程置于分离模式。一个线程的 join() 方法与 detach() 方法只能调用一次,不能在调用了 join() 之后又调用 detach(),也不能在调用 detach() 之后又调用 join(),在调用了 join() 或者 detach() 之后,该线程的 id 即被置为默认值(空线程),表示不能继续再对该线程作修改变化。如果没有调用 join() 或者 detach(),那么,在析构的时候,该线程实例将会调用 std::terminate(),这会导致整个进程退出,所以,如果没有特别需要,一般都建议在生成子线程后调用其 join() 方法等待其退出,这样子最起码知道这些子线程在什么时候已经确保结束。在 C++11 里面没有提供 kill 掉某个线程的能力,只能被动地等待某个线程的自然结束,如果我们要主动停止某个线程的话,可以通过调用 Linux 操作系统提供的 pthread_kill 函数给目标线程发送信号来实现,示例如下:清单 5.例子 thread_kill.ccstatic void on_signal_term(int sig){
cout && "on SIGTERM:" && this_thread::get_id() &&
pthread_exit(NULL);
void threadPosixKill(void){
signal(SIGTERM, on_signal_term);
thread* t = new thread( [](){
while(true){
pthread_t tid = t-&native_handle();
cout && "tid=" && tid &&
// 确保子线程已经在运行。
this_thread::sleep_for( chrono::seconds(1) );
pthread_kill(tid, SIGTERM);
t-&join();
cout && "thread destroyed." &&
}上述例子还可以用来给某个线程发送其它信号,具体的 pthread_exit 函数调用的约定依赖于具体的操作系统的实现,所以,这个方法是依赖于具体的操作系统的,而且,因为在 C++11 里面没有这方面的具体约定,用这种方式也是依赖于 C++编译器的具体实现的。线程类 std::thread 的其它方法和特点thread 类是一个特殊的类,它不能被拷贝,只能被转移或者互换,这是符合线程的语义的,不要忘记这里所说的线程是直接被操作系统调度的。线程的转移使用 move 函数,示例如下:清单 6.例子 thread_move.ccvoid threadMove(void){
int a = 1;
thread t( [](int* pa){
*pa = (*pa * 33) % 0x7
if ( ( (*pa) && 30) & 1)
thread t2 = move(t); // 改为 t2 = t 将不能编译。
t2.join();
cout && "a=" && a &&
}在这个例子中,如果将 t2.join() 改为 t.join() 将会导致整个进程被结束,因为忘记了调用 t2 也就是被转移的线程的 join() 方法,从而导致整个进程被结束,而 t 则因为已经被转移,其 id 已被置空。线程实例互换使用 swap 函数,示例如下:清单 7.例子 thread_swap.ccvoid threadSwap(void){
int a = 1;
thread t( [](int* pa){
*pa = (*pa * 33) % 0x7
if ( ( (*pa) && 30) & 1)
thread t2;
cout && "before swap: t=" && t.get_id()
&& ", t2=" && t2.get_id() &&
swap(t, t2);
cout && "after swap : t=" && t.get_id()
&& ", t2=" && t2.get_id() &&
t2.join();
cout && "a=" && a &&
}互换和转移很类似,但是互换仅仅进行实例(以 id 作标识)的互换,而转移则在进行实例标识的互换之前,还进行了转移目的实例(如下例的t2)的清理,如果 t2 是可聚合的(joinable() 方法返回 true),则调用 std::terminate(),这会导致整个进程退出,比如下面这个例子:清单 8.例子 thread_move_term.ccvoid threadMoveTerm(void){
int a = 1;
thread t( [](int* pa){
*pa = (*pa * 33) % 0x7
if ( ( (*pa) && 30) & 1)
thread t2( [](){
int i = 0;
for(;;)i++;
t2 = move(t); // 将会导致 std::terminate()
cout && "should not reach here" &&
t2.join();
}所以,在进行线程实例转移的时候,要注意判断目的实例的 id 是否为空值(即 id())。如果我们继承了 thread 类,则还需要禁止拷贝构造函数、拷贝赋值函数以及赋值操作符重载函数等,另外,thread 类的析构函数并不是虚析构函数。示例如下:清单 9.例子 thread_inherit.ccclass MyThread : public thread{
MyThread() noexcept : thread(){};
template&typename Callable, typename... Args&
MyThread(Callable&& func, Args&&... args) :
thread( std::forward&Callable&(func),
std::forward&Args&(args)...){
~MyThread() { thread::~thread(); }
// disable copy constructors
MyThread( MyThread& ) =
MyThread( const MyThread& ) =
MyThread& operator=(const MyThread&) =
};因为 thread 类的析构函数不是虚析构函数,在上例中,需要避免出现下面这种情况:MyThread* tc = new MyThread(...);...thread* tp =...这种情况会导致 MyThread 的析构函数没有被调用。线程的调度我们可以调用 this_thread::yield() 将当前调用者线程切换到重新等待调度,但是不能对非调用者线程进行调度切换,也不能让非调用者线程休眠(这是操作系统调度器干的活)。清单 10.例子 thread_yield.ccvoid threadYield(void){
unsigned int procs = thread::hardware_concurrency(), // 获取物理线程数目
thread* ta = new thread( [](){
struct timeval t1, t2;
gettimeofday(&t1, NULL);
for(int i = 0, m = 13; i & COUNT; i++, m *= 17){
this_thread::yield();
gettimeofday(&t2, NULL);
print_time(t1, t2, " with yield");
thread** tb = new thread*[ procs ];
for( i = 0; i & i++){
tb[i] = new thread( [](){
struct timeval t1, t2;
gettimeofday(&t1, NULL);
for(int i = 0, m = 13; i & COUNT; i++, m *= 17){
do_nothing();
gettimeofday(&t2, NULL);
print_time(t1, t2, "without yield");
ta-&join();
for( i = 0; i & i++){
tb[i]-&join();
delete tb[i];
}ta 线程因为需要经常切换去重新等待调度,它运行的时间要比 tb 要多,比如在作者的机器上运行得到如下结果:$time ./a.out
without yield elapse 0.050199s
without yield elapse 0.051042s
without yield elapse 0.05139s
without yield elapse 0.048782s
with yield elapse 1.63366s
real 0m1.643s
user 0m1.175s
sys 0m0.611sta 线程即使扣除系统调用运行时间 0.611s 之后,它的运行时间也远大于没有进行切换的线程。C++11 没有提供调整线程的调度策略或者优先级的能力,如果需要,只能通过调用相关的 pthread 函数来进行,需要的时候,可以通过调用 thread 类实例的 native_handle() 方法或者操作系统 API pthread_self() 来获得 pthread 线程 id,作为 pthread 函数的参数。线程间的数据交互和数据争用 (Data Racing)同一个进程内的多个线程之间多是免不了要有数据互相来往的,队列和共享数据是实现多个线程之间的数据交互的常用方式,封装好的队列使用起来相对来说不容易出错一些,而共享数据则是最基本的也是较容易出错的,因为它会产生数据争用的情况,即有超过一个线程试图同时抢占某个资源,比如对某块内存进行读写等,如下例所示:清单 11.例子 thread_data_race.ccstatic void
inc(int *p ){
for(int i = 0; i & COUNT; i++){
void threadDataRacing(void){
int a = 0;
thread ta( inc, &a);
thread tb( inc, &a);
ta.join();
tb.join();
cout && "a=" && a &&
}这是简化了的极端情况,我们可以一眼看出来这是两个线程在同时对&a 这个内存地址进行写操作,但是在实际工作中,在代码的海洋中发现它并不一定容易。从表面看,两个线程执行完之后,最后的 a 值应该是 COUNT * 2,但是实际上并非如此,因为简单如 (*p)++这样的操作并不是一个原子动作,要解决这个问题,对于简单的基本类型数据如字符、整型、指针等,C++提供了原子模版类 atomic,而对于复杂的对象,则提供了最常用的锁机制,比如互斥类 mutex,门锁 lock_guard,唯一锁 unique_lock,条件变量 condition_variable 等。现在我们使用原子模版类 atomic 改造上述例子得到预期结果:清单 12.例子 thread_atomic.ccstatic void
inc(atomic&int& *p ){
for(int i = 0; i & COUNT; i++){
void threadDataRacing(void){
atomic&int& a(0) ;
thread ta( inc, &a);
thread tb( inc, &a);
ta.join();
tb.join();
cout && "a=" && a &&
}我们也可以使用 lock_guard,lock_guard 是一个范围锁,本质是 RAII(Resource Acquire Is Initialization),在构建的时候自动加锁,在析构的时候自动解锁,这保证了每一次加锁都会得到解锁。即使是调用函数发生了异常,在清理栈帧的时候也会调用它的析构函数得到解锁,从而保证每次加锁都会解锁,但是我们不能手工调用加锁方法或者解锁方法来进行更加精细的资源占用管理,使用 lock_guard 示例如下:清单 13.例子 thread_lock_guard.ccstatic mutex g_
static void
inc(int *p ){
for(int i = 0; i & COUNT; i++){
lock_guard&mutex& _(g_mutex);
void threadLockGuard(void){
int a = 0;
thread ta( inc, &a);
thread tb( inc, &a);
ta.join();
tb.join();
cout && "a=" && a &&
}如果要支持手工加锁,可以考虑使用 unique_lock 或者直接使用 mutex。unique_lock 也支持 RAII,它也可以一次性将多个锁加锁;如果使用 mutex 则直接调用 mutex 类的 lock, unlock, trylock 等方法进行更加精细的锁管理:清单 14.例子 thread_mutex.ccstatic mutex g_
static void
inc(int *p ){
thread_ // TLS 变量
for(; i & COUNT; i++){
g_mutex.lock();
g_mutex.unlock();
void threadMutex(void){
int a = 0;
thread ta( inc, &a);
thread tb( inc, &a);
ta.join();
tb.join();
cout && "a=" && a &&
}在上例中,我们还使用了线程本地存储 (TLS) 变量,我们只需要在变量前面声明它是 thread_local 即可。TLS 变量在线程栈内分配,线程栈只有在线程创建之后才生效,在线程退出的时候销毁,需要注意不同系统的线程栈的大小是不同的,如果 TLS 变量占用空间比较大,需要注意这个问题。TLS 变量一般不能跨线程,其初始化在调用线程第一次使用这个变量时进行,默认初始化为 0。对于线程间的事件通知,C++11 提供了条件变量类 condition_variable,可视为 pthread_cond_t 的封装,使用条件变量可以让一个线程等待其它线程的通知 (wait,wait_for,wait_until),也可以给其它线程发送通知 (notify_one,notify_all),条件变量必须和锁配合使用,在等待时因为有解锁和重新加锁,所以,在等待时必须使用可以手工解锁和加锁的锁,比如 unique_lock,而不能使用 lock_guard,示例如下:清单 15.例子 thread_cond_var.cc#include &thread&
#include &iostream&
#include &condition_variable&
condition_
void threadCondVar(void){
# define THREAD_COUNT 10
thread** t = new thread*[THREAD_COUNT];
for(i = 0; i & THREAD_COUNT; i++){
t[i] = new thread( [](int index){
unique_lock&mutex& lck(m);
cv.wait_for(lck, chrono::hours(1000));
cout && index &&
this_thread::sleep_for( chrono::milliseconds(50));
for(i = 0; i & THREAD_COUNT; i++){
lock_guard&mutex& _(m);
cv.notify_one();
for(i = 0; i & THREAD_COUNT; i++){
t[i]-&join();
delete t[i];
}从上例的运行结果也可以看到,条件变量是不保证次序的,即首先调用 wait 的不一定首先被唤醒。几个高级概念C++11 提供了若干多线程编程的高级概念:promise/future, packaged_task, async,来简化多线程编程,尤其是线程之间的数据交互比较简单的情况下,让我们可以将注意力更多地放在业务处理上。promise/future 可以用来在线程之间进行简单的数据交互,而不需要考虑锁的问题,线程 A 将数据保存在一个 promise 变量中,另外一个线程 B 可以通过这个 promise 变量的 get_future() 获取其值,当线程 A 尚未在 promise 变量中赋值时,线程 B 也可以等待这个 promise 变量的赋值:清单 16.例子 thread_promise_future.ccpromise&string&
static void
threadPromiseFuture(){
thread ta([](){
future&string& fu = val.get_future();
cout && "waiting promise-&future" &&
cout && fu.get() &&
thread tb([](){
this_thread::sleep_for( chrono::milliseconds(100) );
val.set_value("promise is set");
ta.join();
tb.join();
}一个 future 变量只能调用一次 get(),如果需要多次调用 get(),可以使用 shared_future,通过 promise/future 还可以在线程之间传递异常。如果将一个 callable 对象和一个 promise 组合,那就是 packaged_task,它可以进一步简化操作:清单 17.例子 thread_packaged_task.ccstatic mutex g_
static void
threadPackagedTask(){
auto run = [=](int index){
lock_guard&mutex& _(g_mutex);
cout && "tasklet " && index &&
this_thread::sleep_for( chrono::seconds(10) );
return index * 1000;
packaged_task&int(int)& pt1(run);
packaged_task&int(int)& pt2(run);
thread t1([&](){pt1(2);} );
thread t2([&](){pt2(3);} );
int f1 = pt1.get_future().get();
int f2 = pt2.get_future().get();
cout && "task result=" && f1 &&
cout && "task result=" && f2 &&
t1.join();
t2.join();
}我们还可以试图将一个 packaged_task 和一个线程组合,那就是 async() 函数。使用 async() 函数启动执行代码,返回一个 future 对象来保存代码返回值,不需要我们显式地创建和销毁线程等,而是由 C++11 库的实现决定何时创建和销毁线程,以及创建几个线程等,示例如下:清单 18.例子 thread_async.ccstatic long
do_sum(vector&long& *arr, size_t start, size_t count){
static mutex _m;
long sum = 0;
for(size_t i = 0; i & i++){
sum += (*arr)[start + i];
lock_guard&mutex& _(_m);
cout && "thread " && this_thread::get_id()
&& ", count=" && count
&& ", sum=" && sum &&
static void
threadAsync(){
# define COUNT 1000000
vector&long& data(COUNT);
for(size_t i = 0; i & COUNT; i++){
data[i] = random() & 0
vector& future&long& &
size_t ptc = thread::hardware_concurrency() * 2;
for(size_t batch = 0; batch & batch++){
size_t batch_each = COUNT /
if (batch == ptc - 1){
batch_each = COUNT - (COUNT / ptc * batch);
result.push_back(async(do_sum, &data, batch * batch_each, batch_each));
long total = 0;
for(size_t batch = 0; batch & batch++){
total += result[batch].get();
cout && "total=" && total &&
}如果是在多核或者多 CPU 的环境上面运行上述例子,仔细观察输出结果,可能会发现有些线程 ID 是重复的,这说明重复使用了线程,也就是说,通过使用 async() 还可达到一些线程池的功能。几个需要注意的地方thread 同时也是棉线、毛线、丝线等意思,我想大家都能体会面对一团乱麻不知从何处查找头绪的感受,不要忘了,线程不是静态的,它是不断变化的,请想像一下面对一团会动态变化的乱麻的情景。所以,使用多线程技术的首要准则是我们自己要十分清楚我们的线程在哪里?线头(线程入口和出口)在哪里?先安排好线程的运行,注意不同线程的交叉点(访问或者修改同一个资源,包括内存、I/O 设备等),尽量减少线程的交叉点,要知道几条线堆在一起最怕的是互相打结。当我们的确需要不同线程访问一个共同的资源时,一般都需要进行加锁保护,否则很可能会出现数据不一致的情况,从而出现各种时现时不现的莫名其妙的问题,加锁保护时有几个问题需要特别注意:一是一个线程内连续多次调用非递归锁 (non-recursive lock) 的加锁动作,这很可能会导致异常;二是加锁的粒度;三是出现死锁 (deadlock),多个线程互相等待对方释放锁导致这些线程全部处于罢工状态。第一个问题只要根据场景调用合适的锁即可,当我们可能会在某个线程内重复调用某个锁的加锁动作时,我们应该使用递归锁 (recursive lock),在 C++11 中,可以根据需要来使用 recursive_mutex,或者 recursive_timed_mutex。第二个问题,即锁的粒度,原则上应该是粒度越小越好,那意味着阻塞的时间越少,效率更高,比如一个数据库,给一个数据行 (data row) 加锁当然比给一个表 (table) 加锁要高效,但是同时复杂度也会越大,越容易出错,比如死锁等。对于第三个问题我们需要先看下出现死锁的条件:
资源互斥,某个资源在某一时刻只能被一个线程持有 (hold);
吃着碗里的还看着锅里的,持有一个以上的互斥资源的线程在等待被其它进程持有的互斥资源;
不可抢占,只有在某互斥资源的持有线程释放了该资源之后,其它线程才能去持有该资源;
环形等待,有两个或者两个以上的线程各自持有某些互斥资源,并且各自在等待其它线程所持有的互斥资源。我们只要不让上述四个条件中的任意一个不成立即可。在设计的时候,非常有必要先分析一下会否出现满足四个条件的情况,特别是检查有无试图去同时保持两个或者两个以上的锁,当我们发现试图去同时保持两个或者两个以上的锁的时候,就需要特别警惕了。下面我们来看一个简化了的死锁的例子:清单 19.例子 thread_deadlock.ccstatic mutex g_mutex1, g_mutex2;
static void
inc1(int *p ){
for(int i = 0; i & COUNT; i++){
g_mutex1.lock();
g_mutex2.lock();
// do something.
g_mutex2.unlock();
g_mutex1.unlock();
static void
inc2(int *p ){
for(int i = 0; i & COUNT; i++){
g_mutex2.lock();
g_mutex1.lock();
g_mutex1.unlock();
// do other thing.
g_mutex2.unlock();
void threadMutex(void){
int a = 0;
thread ta( inc1, &a);
thread tb( inc2, &a);
ta.join();
tb.join();
cout && "a=" && a &&
}在这个例子中,g_mutex1 和 g_mutex2 都是互斥的资源,任意时刻都只有一个线程可以持有(加锁成功),而且只有持有线程调用 unlock 释放锁资源的时候其它线程才能去持有,满足条件 1 和 3,线程 ta 持有了 g_mutex1 之后,在释放 g_mutex1 之前试图去持有 g_mutex2,而线程 tb 持有了 g_mutex2 之后,在释放 g_mutex2 之前试图去持有 g_mutex1,满足条件 2 和 4,这种情况之下,当线程 ta 试图去持有 g_mutex2 的时候,如果 tb 正持有 g_mutex2 而试图去持有 g_mutex1 时就发生了死锁。在有些环境下,可能要多次运行这个例子才出现死锁,实际工作中这种偶现特性让查找问题变难。要破除这个死锁,我们只要按如下代码所示破除条件 3 和 4 即可:清单 20.例子 thread_break_deadlock.ccstatic mutex g_mutex1, g_mutex2;
static voi
inc1(int *p ){
for(int i = 0; i & COUNT; i++){
g_mutex1.lock();
g_mutex1.unlock();
g_mutex2.lock();
// do something.
g_mutex2.unlock();
static void
inc2(int *p ){
for(int i = 0; i & COUNT; i++){
g_mutex2.lock();
// do other thing.
g_mutex2.unlock();
g_mutex1.lock();
g_mutex1.unlock();
void threadMutex(void){
int a = 0;
thread ta( inc1, &a);
thread tb( inc2, &a);
ta.join();
tb.join();
cout && "a=" && a &&
}在一些复杂的并行编程场景,如何避免死锁是一个很重要的话题,在实践中,当我们看到有两个锁嵌套加锁的时候就要特别提高警惕,它极有可能满足了条件 2 或者 4。结束语上述例子在 CentOS 6.5,g++ 4.8.1/g++4.9 以及 clang 3.5 下面编译通过,在编译的时候,请注意下述几点:
设置 -std=c++11;
链接的时候设置 -pthread;
使用 g++编译链接时设置 -Wl,--no-as-needed 传给链接器,有些版本的 g++需要这个设置;
设置宏定义 -D_REENTRANT,有些库函数是依赖于这个宏定义来确定是否使用多线程版本的。具体可以参考本文所附的代码中的 Makefile 文件。在用 gdb 调试多线程程序的时候,可以输入命令 info threads 查看当前的线程列表,通过命令 thread n 切换到第 n 个线程的上下文,这里的 n 是 info threads 命令输出的线程索引数字,例如,如果要切换到第 2 个线程的上下文,则输入命令 thread 2。聪明地使用多线程,拥抱多线程吧。
相关主题关于 thread 的定义可参考
和 。关于 C++11 标准中 thread 以及其它相关类的声明可以参考草案。参考 Bjarne Stroustrup 的《The C++ Programming Language》第 4 版是 C++11 的权威著作。访问 developerWorks ,了解关于信息管理的更多信息,获取技术文档、how-to 文章、培训、下载、产品信息以及其他资源。
添加或订阅评论,请先或。
有新评论时提醒我
static.content.url=/developerworks/js/artrating/SITE_ID=10Zone=LinuxArticleID=992585ArticleTitle=使用 C++11 编写 Linux 多线程程序publish-date=}

我要回帖

更多关于 装修预算总说明怎么写 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信