12.1 创建一个线程
问题
当主线程继续它的工作时,如何创建一个新的线程来执行某项任务?
解决方案
创建类thread的一个对象,并把一个处理某项工作的函数传给它。这个thread对象的创建将实例化一个操作系统的线程,这个操作系统的线程将执行你传入的函数operator()方法(或者如果你传入的是一个函数指针的话,将从这个函数的开始处开始执行)。
示例12-1 创建一个线程
#include <iostream>
#include <boost/thread/thread.hpp>
#include <boost/thread/xtime.hpp>
struct MyThreadFunc {
void operator()() {
// Do something long-running...
}
} threadFun;
int main() {
boost::thread myThread(threadFun); // Create a thread that starts
// running threadFun
boost::thread::yield(); // Give up the main thread's timeslice
// so the child thread can get some work
// done.
// Go do some other work...
myThread.join(); // The current (i.e., main) thread will wait
// for myThread to finish before it returns
}
讨论
创建一个线程是比较简单的。你所需要做的就是在栈空间或堆空间上创建一个线程对象,并且把你需要做的工作的函数传给它。从讨论的角度来说,一个线程有两方面的含义。第一,它是thread类的一个对象,从传统的意义上来说它是一个C++对象。当我引用对象时,我的意思是它是一个线程对象。然后,它又是一个执行的线程,也就是说通过这个线程对象它代表了操作系统的线程。当我说线程时,我是指操作系统的线程。
让我们看一看例子中的代码。这个thread类的构造函数带一个算符(或者是函数指针),这个算符不带参数并返回void类型。请看示例12-1中的代码:
boost::thread myThread(threadFun);
这就在栈上创建一个名为myThread的对象,它代表操作系统的一个新的线程并开始执行这个传入的函数threadFun。在这一点上,至少从理论上说,threadFun算符中的代码和main函数中的代码是并行执行的。精确地说,这两个线程并可能不是并行执行的,因为你的机器可能只有一个处理器,在这种情况下是不可能并行执行的(最近的处理器体系结构可能不是这样,但是我不考虑那些双核处理器)。如果你只有一个处理器,那么操作系统将给你创建的处在运行run状态的线程在这个线程挂起前分配一个时间片。因为这些时间片可能在大小上有差别,所以你不可能保证某个线程在某个确定的点执行。这就是多线程编程中困难的地方:多线程程序的执行是不确定的。同一个多线程程序在相同的输入的情况下,多次运行的结果可能不同。多线程中资源访问的协调就是12.2节的主题。
创建完myThread后,主线程继续运行,最少一会以后,直到它碰到如下这行代码行:
boost::thread::yield();
这行代码将使得当前线程(这个例子中是主线程)进入sleep睡眠状态,也就意味着操作系统将使用特定于操作系统的策略来交换另外一个线程或另外一个进程。yield方法的功能就是告诉操作系统当前的线程需要放弃余下的时间片。同时,一个新的线程执行线程函数threadFun。但这个thread执行完时,这个子线程就会消失。注意这个线程对象不会消失,因为它仍然是一个还处在它的生存期的C++对象。这是一个重要的特性。
这个线程对象存在于堆或栈上,就像任何其他的C++对象那样。当这些调用的代码退出了它的范围时,任何栈上的线程对象就会被销毁掉,并且当调用者使用一个指向线程的指针作为参数调用delete方法时,相应的堆上的线程对象也会被销毁掉。但是线程对象事实上仅仅是操作系统线程的代理,并且当这些线程对象被销毁掉时,操作系统线程并不能保证就消失。它们仅仅是从这个对象中分离开了,也就意味着这些线程以后是不能再join的。这点并不坏。
线程使用资源,并且在任何一个设计良好的多线程应用程序中,访问这些资源(对象、网络连接、文件、内存等)都需要使用互斥体mutex来控制,这些互斥体是一些使用来让这些线程串行化访问资源的对象(请看12.2节)。如果一个操作系统上的线程被清除掉的话,它将不释放它的锁或释放它的资源,相同地杀死一个进程也不会有机会来擦除新缓存或者正确地释放操作系统的资源。当你设想一个线程应该结束时就简单地结束一个线程就如同当一个油漆匠的时间用完时你就把它用的梯子从他下面移走一样。
因此,我们就有了join成员函数。就像在示例12-1中那样,你可以调用join方法来等着它的子线程结束。join是一个礼貌的方法,它告诉线程你正在等待直到它做完它的工作:
myThread.join();
这个调用join方法的线程进入wait等待状态直到myThread代表的线程完成为止。如果它不结束的话,join方法就不会返回。join是一个等待子线程结束的最好的方法。
你可能注意到如果你在threadFun中实现一些有意义的事情,但把join方法的使用代码注释起来的话,线程将不能完成它的工作。在这个threadFun中放入一个循环或某些比较费时的工作试试看。这是因为当操作系统销毁掉一个进程时,它的所有子线程也将随它而去,而不会管它们是否已经完成了。不调用join方法,主线程就不会等待它的子线程:主线程退出,并且操作系统意义上的线程就被销毁了。
如果你需要创建几个线程,考虑使用一个线程组对象thread_group来组织它们。一个thread_group对象可以使用多种办法来管理线程。首先,你可以使用一个指向一个线程对象的指针作为参数来调用add_thread方法,然后这个线程对象就被加入到这个线程组中。如下一个例子所示:
boost::thread_group grp;
boost::thread* p = new boost::thread(threadFun);
grp.add_thread(p);
// do something...
grp.remove_thread(p);
当这个grp的析构函数被调用时,它将删除所有这些通过add_thread方法加入的线程指针。由于这个原因,你仅能把一个堆上的线程对象的指针加入到一个线程组thread_group中。调用remove_thread方法并给这个方法传递一个线程对象的地址则从这个线程组中删除一个线程(remove_thread通过比较指针的值而不是这些指针所指向对象来在这个线程组中找到相应的指针对象)。remove_thread方法从线程组中删除某个线程的指针,但是你仍然负责把线程本身删除。
你可以调用create_thread方法,先不创建某个线程而直接把线程加入到线程组中,也就意味着这个方法(同一个线程对象一样)带一个函数算符作为参数并且创建一个新的操作系统上的线程。例如,在一个线程组中创建两个线程,如下所示:
boost::thread_group grp;
grp.create_thread(threadFun);
grp.create_thread(threadFun); // Now there are two threads in grp
grp.join_all(); // Wait for all threads to finish
无论是你通过create_thread还是add_thread方法把线程加到线程组,你都可以调用join_all来等待这个线程组中的所有线程执行完。调用join_all方法在功能上与你在这个线程组中的每一个线程上调用join方法完成的功能一样:当这个线程组中所有线程的工作都完成时,join_all方法返回。
创建一个线程对象让一个单独的线程开始执行。使用Boost线程库这样做是非常容易的,尽管设计时还是要非常仔细。更多的有关线程的警戒信息请参考这一章的其他几节。
请参考12.2节。






