C++11开始支持多线程编程,之前多线程编程都需要系统的支持,在不同的系统下创建线程需要不同的API如pthread_create(),Createthread(),beginthread()等。现在C++11中引入了一个新的线程库,C++11提供了新头文件,主要包含 <thread>、<mutex>、<atomic>、<condition_varible>、<future>五个部分;<thread>等用于支持多线程,同时包含了用于启动、管理线程的诸多工具,同时,该库还提供了包括像互斥量、锁、原子量等在内的同步机制。在这个系列的文章中,我将尝试和大家分享这个新库提供的部分特性
为了让你的代码支持多线程库,你环境上的编译器必须支持 C++11,不过幸运的是现在绝大部分的编译器都支持C++11了,除非你手头上的编译器版本是古董级别了,真这样的话,还是建议你赶快升级,不光是为了这次的学习,后面你肯定会用上的。
在看本文之前,如果对什么是线程、什么是进程仍存有疑惑,请先Google之,这两个概念不在本文的范围之内。用多线程只有一个目的,那就是更好的利用cpu的资源,下面两个图很好的反应了这点。
以单cpu为例,假如有个进程,进程功能是从网络上接受数据,将接收到的数据进行处理。我们知道,网络上的数据是有时延的,甚至在你接收数据的这段时间内由于网络等问题接收不到数据,如果是单线程的进程,那么此时你的进程什么也做不了,苦苦的在等待对端的数据。此时cpu被浪费了(假设系统就这一个进程,不考虑将进程切换到其它进程的)。
如果进程含有两个线程,一个线程专门接收数据,暂且叫做A线程吧;另外一个线程专门负责处理接收的数据,就叫B线程吧。那么情况就不一样了,当由于网络等问题接收不到数据时,阻塞的只是A线程,完全可以通过某种调度机制将cpu调度给B线程让其工作。这样,cpu得到了最大的利用而不至于浪费在那儿。
好了,既然线程的用处这么大,C++11又加了支持多线程的库,那么让我们一窥其庐山真面目吧。
如何启动一个线程
在C++11中,启动一个新的线程非常简单,当你创建一个 std::thread 的实例时,它便会自行启动。同时在创建线程实例时,须提供给该线程一段将要执行的函数,方法之一是在创建线程实例传递一个函数指针。
好吧,不管学什么编程语言,Hello world!感觉总是不会少的,这仿佛已经成为了一个标准。这次我们仍以经典的 "Hello world” 为例来向大家如何创建一个线程:
#include <thread> #include <iostream> void hello() { std::cout << "Hello world from thread !" << std::endl; } int main() { std::thread t(hello); t.join(); return 0; }
[[email protected] c++]# g++ -std=c++11 -o a a.cpp [[email protected] c++]# ./a terminate called after throwing an instance of 'std::system_error' what(): Enable multithreading to use std::thread: Operation not permitted 已放弃
云行时报错了,编译的时候加上-lpthread
[[email protected] c++]# g++ -std=c++11 -lpthread -o a a.cpp [[email protected] c++]# ./a Hello world from thread !
看到了吧,首先,我们引入了头文件<thread>。在这个头文件中,C++11 提供了管理线程的类和函数。之后,我们定义了一个无返回类型的函数hello,这个函数除了在标准输出流中打印一行文字之外什么也不做。而后,我们定义了一个 std::thread 类型的变量 t,在定义的时候用hello函数名(其实就是一个函数指针)作为 std::thread 类构造函数的参数 。
这个例子中值得注意的是函数 join()。调用join()将导致当前线程等待被 join 的线程结束(在本例中即线程 main 必须等待线程 t结束后方可继续执行)。如果你不调用 join() ,其结果是未定义的 —— 程序可能如你所愿打印出 "Hello world from thread" 以及一个换行,但是也可能只打印出 "Hello world from thread" 却没有换行,甚至什么都不做,那是因为线程 main 可能在线程 t结束之前就返回了。
其次还需注意的就是在编译的时候需加上参数-std=c++11,否则编译不过。
使用Lambda表达式启动线程
如果线程所要执行的代码非常短小时,你完全没必要专门为之创建一个函数,取而代之的是使用 Lambda 表达式(关于Lambda 表达式,有兴趣的朋友可以上网查查它的用法,它也是C++11新加入的特性)。我们可以很轻易地将上述例子改写为使用 Lambda 表达式的形式:
#include <thread> #include <iostream> int main() { std::thread t([](){ std::cout << "Hello world from thread !"<< std::endl;}); t.join(); return 0; }
[[email protected] c++]# ./b
Hello world from thread !
如上,我们使用了 Lambda 表达式替换掉原来的函数指针。不需要任何怀疑,这段代码和之前使用函数指针的代码实现了完全相同的功能。
是不是觉得开启一个线程就是为了打印一句话有点小题大做是吧,好的,我们现在是该做点什么了。在上个线程函数的基础上,除了打印一句话,我们再加个功能:接受两个参数,计算它们的和并打印出。
#include <thread> #include <iostream> //线程执行函数接受两个int类型参数 void hello(int x,int y) { std::cout<<"Hello world from thread !" <<std::endl; std::cout<<"x+y=" <<x+y <<std::endl; } int main() { std::thread t (hello,10,20); t.join(); return 0; }
[[email protected] c++]# ./cHello world from thread !x+y=30
程序的运行结果正是我们想要的,没错,向线程函数传参就是这么简单:在创建线程实例的时候和函数指针一起传递过去
区分线程
我们可以回一下我们是如何区分进程的?每个进程都有一个编号,称为pid(进程id),我们就是通过它来区分不同的进程,不光是我们人,其实操作系统也是通过这个pid来区分管理不同的进程。
线程也一样, 每个线程都有唯一的 ID 以便我们加以区分,我们称之为tid(线程id)。使用 std::this_thread 类的 get_id() 便可获取对应线程的tid。下面的例子将创建一些线程并使用 std::this_thread::get_id() 来获取当前线程的tid,并打印出:
#include <thread> #include <iostream> #include <vector> void hello() { std::cout << "Hello world from thread: " << std::this_thread::get_id() << std::endl; } int main() { std::vector<std::thread> threads; for(int i = 0; i < 6; ++i) { threads.push_back(std::thread(hello)); } for(auto& thread : threads) { thread.join(); } return 0; }
[[email protected] c++]# ./thread Hello world from thread: Hello world from thread: Hello world from thread: Hello world from thread: 140598791485184 140598816663296140598799877888 Hello world from thread: 140598783092480 Hello world from thread: 140598774699776 140598808270592 [[email protected] c++]# ./thread Hello world from thread: 140198417712896 Hello world from thread: 140198409320192 Hello world from thread: 140198392534784 Hello world from thread: 140198375749376 Hello world from thread: 140198384142080 Hello world from thread: 140198400927488
看到没,上述结果我肯定不是你所希望看到的,结果似乎混乱了,如果你在你的电脑上多运行几遍,甚至还会出现其它各种结果,那么这种匪夷所思的运行结果究竟是什么原因导致的?
我们知道线程之间是交叉运行的,在上面这个例子,我们并没有去控制线程的执行顺序,某个线程在运行期间可能随时被抢占, 同时可以看到,上面例子的ostream 分几个步骤(首先输出一个 string,然后是 ID,最后输出换行),因此三个线程可能先都只执行了第一个步骤将 Hello world from thread 打印出来,然后每个线程都依次执行完剩余的两个步骤(打印ID,然后换行),这就导致了上面的运行结果。
那么有没有方法解决上面的问题了,答案是肯定的,接下来我们将看到如何使用锁机制控制线程的执行顺序。
#include <thread> #include <iostream> #include <vector> pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; void hello() { pthread_mutex_lock(&lock); std::cout << "Hello world from thread: " << std::this_thread::get_id() << std::endl; pthread_mutex_unlock(&lock); } int main() { std::vector<std::thread> threads; for(int i = 0; i < 6; ++i) { //pthread_mutex_lock(&lock); threads.push_back(std::thread(hello)); //pthread_mutex_unlock(&lock); } for(auto& thread : threads) { thread.join(); } return 0; }
[[email protected] c++]# ./thread Hello world from thread: 140515972613888 Hello world from thread: 140515939043072 Hello world from thread: 140515930650368 Hello world from thread: 140515964221184 Hello world from thread: 140515955828480 Hello world from thread: 140515947435776 [[email protected] c++]# ./thread Hello world from thread: 140654524454656 Hello world from thread: 140654532847360 Hello world from thread: 140654516061952 Hello world from thread: 140654507669248 Hello world from thread: 140654499276544 Hello world from thread: 140654490883840
看到没有,现在再执行thread程序的时候,就不会乱了。
转载了码农有道,并把后部分完成了。
原文地址:http://blog.51cto.com/59090939/2055150