现在的位置: 首页 > 综合 > 正文

[Boost基础]并发编程——Thread多线程(一)

2013年10月01日 ⁄ 综合 ⁄ 共 5221字 ⁄ 字号 评论关闭
文章目录

并发编程

并发编程:C++中没有语言级别的并发支持,因为在C++诞生很久以后线程等用于并发操作的概念才出现(POSIX线程标准制定与1955)。但在现在随着拥有多CPU,多内核的计算机的大量出现,C++特别是C++标准没有定义并发操作的规范就显得有些过时了。并发编程是一个很广泛的话题,同时又是一门很复杂的技术。

Boost中有两个用于并发编程的组件。首先是thead库:它为C++增加了可移植的线程处理能力。然后是一个用于同步和异步IO操作的功能强大的库——asio,它使用了前摄器模式,可以处理串口,网络通信,而且有望成为C++标准底层通信库。

thread多线程

thread库为C++增加了线程处理的能力,它提供了简明清晰的线程、互斥量等概念,可以很容易的创建多线程应用程序。thread库也是高度移植的,它支持使用最广泛的windows和POSIX线程,用它编写的代码不需要修改就可以在windows、UNIX等操作系统上运行。这里只讲述thread库的使用,而不讲述如何编写一个(好的)多线程程序,那需要另外一本书。

A.时间功能

在多线程编程是经常要用到超时处理,需要表示时间的概念。thread库直接利用date_time库提供了对时间的支持,可以使用millisec/milliseconds、microsec/microsecond等时间长度来表示超时的时间,或者用ptime表示某个确定的时间点。

B.互斥量

互斥量是一种用于线程同步的手段,它可以在线程编程中防止多个线程同时操作共享资源(或称临界区)。一旦一个线程锁定了互斥量,那么其他线程必须等待他解锁互斥量才能在访问共享资源。thead提供了7中互斥量类型,分别是:

  • 1.mutex:独占式的互斥量,是最简单的最常用的一种互斥量类型;
  • 2.try_mutex:他是mutex的同义词,为了与兼容以前的版本而提供
  • 3.timed_mutex:它也是独占式的互斥量,但提供超时锁定功能;
  • 4.recursive_mutex:递归互斥量,可以多次锁定,相应的也要多次解锁;
  • 5.recursive_try_mutex:它是recursive_mutex的同义词,为了与兼容以前的版本而提供。
  • 6.recursive_timed_mutex:它也是递归式互斥量,基本功能同recursive_mutex,但提供了超时锁定功能
  • 7.shared_mutex:multiple-reader/single-writer型的共享互斥量(又称读写锁)   

它们的成员函数大致如下:

  • 1.lock()用于线程阻塞等待直至获得互斥量的所有权(即锁定)
  • 2.try_lock() 尝试锁定互斥量,如果锁定成功返回true,否则返回false,它是非阻塞的
  • 3.unlock() 当线程使用完共享资源后应该及时使用unlock()解锁对互斥量的锁定。
  • 4.time_lock()  只属于timed_mutex和recursive_timed_mutex,它的行为结合了lock()和try_lock(),阻塞等待一定的时间尝试锁定互斥量,如果时间到还为锁定则返回false。等待时间可以是绝对时间(一个UTC时间点),也可以使从当前开始的相对时间(时间长度)

C.线程对象

thread类是thread库的核心类,负责启动和管理线程对象,在概念和操作上都与POSIX线程很相似。    
在使用thread对象是需要注意它是不可拷贝的,虽然它没有从boost::noncopyable继承,但thread内部把拷贝构造函数和赋值操作都声明为私有的,不能对它进行赋值或者拷贝构造。 
thread通过特别的机制支持转移语义,因此我们可以编写创建线程的工厂函数,封装thread的创建细节,返回一个thread对象。

D.创建线程

从某种程度来说,线程就是在进程中的另一个空间里运行的一个函数,因此线程的创建需要传递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());  
}

 

抱歉!评论已关闭.