并发编程:C++中没有语言级别的并发支持,因为在C++诞生很久以后线程等用于并发操作的概念才出现(POSIX线程标准制定与1955年)。但在现在随着拥有多CPU,多内核的计算机的大量出现,C++特别是C++标准没有定义并发操作的规范就显得有些过时了。并发编程是一个很广泛的话题,同时又是一门很复杂的技术。
Boost中有两个用于并发编程的组件。首先是thead库:它为C++增加了可移植的线程处理能力。然后是一个用于同步和异步IO操作的功能强大的库——asio,它使用了前摄器模式,可以处理串口,网络通信,而且有望成为C++标准底层通信库。
thread库为C++增加了线程处理的能力,它提供了简明清晰的线程、互斥量等概念,可以很容易的创建多线程应用程序。thread库也是高度移植的,它支持使用最广泛的windows和POSIX线程,用它编写的代码不需要修改就可以在windows、UNIX等操作系统上运行。这里只讲述thread库的使用,而不讲述如何编写一个(好的)多线程程序,那需要另外一本书。
在多线程编程是经常要用到超时处理,需要表示时间的概念。thread库直接利用date_time库提供了对时间的支持,可以使用millisec/milliseconds、microsec/microsecond等时间长度来表示超时的时间,或者用ptime表示某个确定的时间点。
互斥量是一种用于线程同步的手段,它可以在线程编程中防止多个线程同时操作共享资源(或称临界区)。一旦一个线程锁定了互斥量,那么其他线程必须等待他解锁互斥量才能在访问共享资源。thead提供了7中互斥量类型,分别是:
它们的成员函数大致如下:
thread类是thread库的核心类,负责启动和管理线程对象,在概念和操作上都与POSIX线程很相似。
在使用thread对象是需要注意它是不可拷贝的,虽然它没有从boost::noncopyable继承,但thread内部把拷贝构造函数和赋值操作都声明为私有的,不能对它进行赋值或者拷贝构造。
thread通过特别的机制支持转移语义,因此我们可以编写创建线程的工厂函数,封装thread的创建细节,返回一个thread对象。
从某种程度来说,线程就是在进程中的另一个空间里运行的一个函数,因此线程的创建需要传递thread对象,一个无参的可调用物(函数或函数对象),它必须具有operator()以供线程执行。
如果可调用物不是无参的,那么thread的构造函数也支持直接传递所需的参数,这些参数被拷贝并在发生调用时传递给函数。这是一个非常体贴方便的重载构造函数,比传统的使用void*来传递参数要好很多。thread的构造函数支持最多传递9个参数,这通常足够用了。
在传递参数时需要注意,thread使用的是参数的拷贝,因此要求可调用物和参数类型都支持拷贝。如果希望传递给线程引用值就需要使用ref库进行包装,同时必须保证被引用的对象在线程执行期间一直存在,否则会引发未定义行为。
#pragma once #include <boost/thread.hpp> #include <boost/bind.hpp> #include <string> #include <conio.h> using namespace boost; //A.时间功能 void test1() { this_thread::sleep(posix_time::seconds(10)); std::cout<<"sleep 2 sconds"<<std::endl; //为了更好地表述时间的线程相关含义,thread库重新定义了一个新的时间类型system_time,它是posix_time::ptime的同义词:typedef boost::posix_time::ptime system_time;。同时也提供了get_system_time()函数,它调用microsec_clock类方便的获取当前的UTC时间值。 boost::system_time st=get_system_time(); printf("system_time: %d\n",st); } //B.互斥量 //定义实现一个原子操作的计数器,它可以安全的在多线程环境下正确的计数。 template <typename T> class basic_atom : boost::noncopyable { private: T n; typedef mutex mutex_t;//互斥量类型定义 mutex_t mu; public: basic_atom(T x=T()) : n(x){}//构造函数 T operator++()//前置式递增操作符 { mutex_t::scoped_lock lock(mu);//锁定互斥量 return ++n; } operator T(){return n;}//类型转换操作符定义 }; void test2() { //basic_atom是一个模板类,因此可以配合模板参数提供不同范围的计数,并且他提供了隐身类型转换操作,;用起来就像是一个普通的整数。 basic_atom<int> x(2); std::cout<<++x<<std::endl; //mutex的基本用法 mutex mu; //声明一个互斥量对象 try { mu.lock();//锁定互斥量 std::cout<<"some operations"<<std::endl;//临界区操作 //输出 3 mu.unlock(); //解锁互斥量 } catch(thread_interrupted&)//必须使用try-catch块保证解锁互斥量 { mu.unlock(); std::cout<<"unlock"<<std::endl; } //直接使用mutex的成员函数来解锁互斥量不够方便,而且在发生异常导致退出作用域等情况下很可能会忘记解除锁定;因此thread库又提供了一系列RAII型的lock_guard类,用于辅助锁定互斥量。它们在构造是锁定互斥量,在析构是自动解锁,从而保证了互斥量的正确操作,避免遗忘解锁,就是一个智能指针。mutex类使用内部类型定义scoped_lock和scoped_try_lock定义了两种lock_guard对象,分别应对执行lock()和try_lock()。如上可以修改为: // mutex mud; // mutex::scoped_lock lock(mud); // std::cout<<"some operations"<<std::endl; } //D.创建线程 mutex muIO;//io流是一个共享资源,不是线程;安全的,需要锁定 void printing(basic_atom<int>& x,const std::string& str) { for (int i=0;i<5;i++) { mutex::scoped_lock lock(muIO);//锁定io流操作 std::cout<<str<<++x<<std::endl; } } void test3() { //1.启动线程 //当成功创建了一个thread对象后,线程就立刻开始执行,thread不提供类似start(),begin()那样的方法。 basic_atom<int> x;//原子操作的计数器 //使用临时thead对象启动线程 thread(printing,ref(x),"hello ");//向函数传递多个参数 thread(printing,ref(x),"boost ");//使用ref库传递引用 this_thread::sleep(posix_time::seconds(2));//等待2秒钟 //注意最后一行代码,在当线程启动后我们必须调用sleep()来等待线程执行结束,否则会因为mian()的return语句导致主线程结束,而其他的线程还没有机会运行而一并结束。通常不应该使用这种“死”等线程结束的方法,因为不可能精确的指定线程会执行多少时间,我们需要用其他更好的方法来等待线程结束。 //2.join和timed_join //thread的成员函数joinable()可以判断thread对象是否标识了一个可执行的线程体。如果joinable()返回true,我们可以调用成员函数join()或者timed_join()阻塞等待线程执行结束。 basic_atom<int> y; thread t1(printing,ref(y),"helloy"); thread t2(printing,ref(y),"boosty"); t1.timed_join(posix_time::seconds(1));//最多等待1秒然后返回 t2.join();//等待t2线程结束才返回,不过执行多少时间 //3.与线程执行体分离 //可以使用成员函数detach()将thread与线程执行体手动分离,此后thread对象不代表任何线程体,失去对线程体的控制 thread t3(printing,ref(x),"helloz");//启动线程 t3.detach();//与线程执行体分离,但线程继续运行 if (t3.joinable()) //返回false //joinable()可以判断thread对象是否标识了一个可执行的线程体。 std::cout<<"t3 is joinable"<<std::endl; //没有输出 //当thread与线程执行体分离时,线程执行体将不受影响地继续执行,直到运行结束,或者随主线程一起结束。当线程执行完毕或者thread对象被销毁时,thread对象也会自动与线程执行体分离,因此,当不需要操作线程体时,我们可以使用临时对象来启动一个线程。eg:thread(printing,ref(x),"hello ");//向函数传递多个参数 //4.使用bind和function //有时在thread的构造函数中写传递给调用函数的参数很麻烦,尤其是在使用大量线程对象的时候。这时我们可以使用bind库和function库:bind库可以把函数所需要的参数绑定到一个函数对象,而function则可以存储bind表达式的结果,供程序以后使用。 boost::thread t4(boost::bind(printing,ref(x),std::string("thread")));//bind表达式 boost::function<void()> fun=boost::bind(printing,ref(x),std::string("mutex")); thread t5(fun);//使用function对象 } void test(char t) { std::cout<<"press key====="<<t<<std::endl; switch (t) { case '1':test1();break; case '2':test2();break; case '3':test3();break; case 27: case 'q':exit(0);break; default: std::cout<<"default "<<t<<std::endl;break; } } void main() { while(1) test(getch()); }