随笔-5  评论-31  文章-0  trackbacks-0
  置顶随笔

协程

协程,即协作式程序,其思想是,一系列互相依赖的协程间依次使用CPU,每次只有一个协程工作,而其他协程处于休眠状态。协程可以在运行期间的某个点上暂停执行,并在恢复运行时从暂停的点上继续执行。 协程已经被证明是一种非常有用的程序组件,不仅被python、lua、ruby等脚本语言广泛采用,而且被新一代面向多核的编程语言如golang rust-lang等采用作为并发的基本单位。 协程可以被认为是一种用户空间线程,与传统的线程相比,有2个主要的优点:

  • 与线程不同,协程是自己主动让出CPU,并交付他期望的下一个协程运行,而不是在任何时候都有可能被系统调度打断。因此协程的使用更加清晰易懂,并且多数情况下不需要锁机制。
  • 与线程相比,协程的切换由程序控制,发生在用户空间而非内核空间,因此切换的代价非常小。

网络编程模型

首先来简单回顾一下一些常用的网络编程模型。网络编程模型可以大体的分为同步模型和异步模型两类。

  • 同步模型:

同步模型使用阻塞IO模式,在阻塞IO模式下调用read等IO函数时会阻塞线程直到IO完成或失败。

同步模型的典型代表是thread per connection模型,每当阻塞在主线程上的accept调用返回时则创建一个新的线程去服务于新的socket的读/写。这种模型的优点是程序简洁,编写简单;缺点是可伸缩性收到线程数的限制,当连接越来越多时,线程也越来越多,频繁的线程切换会严重拖累性能。

  • 异步模型:

异步模型一般使用非阻塞IO模式,并配合epoll/select/poll等多路复用机制。在非阻塞模式下调用read,如果没有数据可读则立即返回并通知用户没有可读(EAGAIN/EWOULDBLOCK),而非阻塞当前线程。异步模型可以使一个线程同时服务于多个IO对象。

异步模型的典型代表是reactor模型。在reactor模型中,我们将所有要处理的IO事件注册到一个中心的IO多路复用器中(一般为epoll/select/poll),同时主线程阻塞在多路复用器上。一旦有IO事件到来或者就绪,多路复用器返回并将对应的IO事件分发到对应的处理器(即回调函数)中,最后处理器调用read/write函数来进行IO操作。

异步模型的特点是性能和可伸缩性比同步模型要好很多,但是其结构复杂,不易于编写和维护。在异步模型中,IO之前的代码(IO任务的提交者)和IO之后的处理代码(回调函数)是割裂开来的。

协程与网络编程

协程为克服同步模型和异步模型的缺点,并结合他们的优点提供了可能: 现在假设我们有3个协程A,B,C分别要进行数次IO操作。这3个协程运行在同一个调度器或者说线程的上下文中,并依次使用CPU。调度器在其内部维护了一个多路复用器(epoll/select/poll)。

协程A首先运行,当它执行到一个IO操作,但该IO操作并没有立即就绪时,A将该IO事件注册到调度器中,并主动放弃CPU。这时调度器将B切换到CPU上开始执行,同样,当它碰到一个IO操作的时候将IO事件注册到调度器中,并主动放弃CPU。调度器将C切换到cpu上开始执行。当所有协程都被“阻塞”后,调度器检查注册的IO事件是否发生或就绪。假设此时协程B注册的IO时间已经就绪,调度器将恢复B的执行,B将从上次放弃CPU的地方接着向下运行。A和C同理。

这样,对于每一个协程来说,是同步的模型;但是对于整个应用程序来说,却是异步的模型。

好了,原理说完了,我们来看一个实际的例子,echo server。

echo server

在这个例子中,我们将使用orchid库来编写一个echo server。orchid库是一个构建于boost基础上的 协程/网络IO 库。

echo server首先必须要处理连接事件,我们创建一个协程来专门处理连接事件:

typedef boost::shared_ptr<orchid::socket> socket_ptr; 
//处理ACCEPT事件的协程
void handle_accept(orchid::coroutine_handle co) {
try {
         orchid::acceptor acceptor(co -> get_scheduler().get_io_service());//构建一个acceptor
acceptor.bind_and_listen("5678",true);
         for(;;) {
            socket_ptr sock(new orchid::socket(co -> get_scheduler().get_io_service()));
acceptor.accept(*sock,co);
            //在调度器上创建一个协程来服务新的socket。第一个参数是要创建的协程的main函数,第二个参数是要创建的协程的栈的大小。
            co -> get_scheduler().spawn(boost::bind(handle_io,_1,sock),orchid::minimum_stack_size());
}
} catch(boost::system::system_error& e) {
            cerr<<e.code()<<" "<<e.what()<<endl;
}
}

在orchid中,协程的main函数必须满足函数签名void(orchid::coroutine_handle),如handle_accept所示,其中参数co是协程句柄,代表了当前函数所位于的协程。

在上面的代码中,我们创建了一个acceptor,并让它监听5678端口,然后在"阻塞"等待连接到来,当连接事件到来时,创建一个新的协程来服务新的socket。处理套接字IO的协程如下:

//处理SOCKET IO事件的协程 
void handle_io(orchid::coroutine_handle co,socket_ptr sock) {
   orchid::tcp_ostream out(*sock,co);
   orchid::tcp_istream in(*sock,co);
   for(std::string str;std::getline(in, str) && out;) {
      out<<str<<endl;
   }
}

IO处理协程首先在传入的套接字上创建了一个输入流和一个输出流,分别代表了TCP的输入和输出。然后不断地从输入流中读取一行,并输出到输出流当中。当socket上的TCP连接断开时,输入流和输出流的eof标志为会被置位,因此循环结束,协程退出。

orchid可以使用户以流的形式来操作套接字。输入流和输出流分别提供了std::istream和std::ostream的接口;输入流和输出流是带缓冲的,如果用户需要无缓冲的读写socket或者自建缓冲,可以直接调用orchid::socket的read和write函数。但是需要注意这两个函数会抛出boost::system_error异常来表示错误。

细心的读者可能已经发现,handle_io的函数签名并不满足void(orchid::coroutine_handle),回到handle_accept中,可以发现,实际上我们使用了boost.bind对handle _ io函数进行了适配,使之符合函数签名的要求。

最后是main函数:

int main() {     
   orchid::scheduler sche;
   sche.spawn(handle_accept,orchid::coroutine::minimum_stack_size());//创建协程
   sche.run();
}

在上面这个echo server的例子中,我们采用了一种 coroutine per connection 的编程模型,与传统的 thread per connection 模型一样的简洁清晰,但是整个程序实际上运行在同一线程当中。

由于协程的切换开销远远小于线程,因此我们可以轻易的同时启动上千协程来同时服务上千连接,这是 thread per connection的模型很难做到的;在性能方面,整个底层的IO系统实际上是使用boost.asio这种高性能的异步io库实现的。而且与IO所费的时间相比,协程切换的开销基本可以忽略。

因此通过协程,我们可以在保持同步IO模型简洁性的同时,获得近似于异步IO模型的高性能。

posted @ 2013-01-01 13:14 江浸月 阅读(6031) | 评论 (5)编辑 收藏
  2013年1月1日

协程

协程,即协作式程序,其思想是,一系列互相依赖的协程间依次使用CPU,每次只有一个协程工作,而其他协程处于休眠状态。协程可以在运行期间的某个点上暂停执行,并在恢复运行时从暂停的点上继续执行。 协程已经被证明是一种非常有用的程序组件,不仅被python、lua、ruby等脚本语言广泛采用,而且被新一代面向多核的编程语言如golang rust-lang等采用作为并发的基本单位。 协程可以被认为是一种用户空间线程,与传统的线程相比,有2个主要的优点:

  • 与线程不同,协程是自己主动让出CPU,并交付他期望的下一个协程运行,而不是在任何时候都有可能被系统调度打断。因此协程的使用更加清晰易懂,并且多数情况下不需要锁机制。
  • 与线程相比,协程的切换由程序控制,发生在用户空间而非内核空间,因此切换的代价非常小。

网络编程模型

首先来简单回顾一下一些常用的网络编程模型。网络编程模型可以大体的分为同步模型和异步模型两类。

  • 同步模型:

同步模型使用阻塞IO模式,在阻塞IO模式下调用read等IO函数时会阻塞线程直到IO完成或失败。

同步模型的典型代表是thread per connection模型,每当阻塞在主线程上的accept调用返回时则创建一个新的线程去服务于新的socket的读/写。这种模型的优点是程序简洁,编写简单;缺点是可伸缩性收到线程数的限制,当连接越来越多时,线程也越来越多,频繁的线程切换会严重拖累性能。

  • 异步模型:

异步模型一般使用非阻塞IO模式,并配合epoll/select/poll等多路复用机制。在非阻塞模式下调用read,如果没有数据可读则立即返回并通知用户没有可读(EAGAIN/EWOULDBLOCK),而非阻塞当前线程。异步模型可以使一个线程同时服务于多个IO对象。

异步模型的典型代表是reactor模型。在reactor模型中,我们将所有要处理的IO事件注册到一个中心的IO多路复用器中(一般为epoll/select/poll),同时主线程阻塞在多路复用器上。一旦有IO事件到来或者就绪,多路复用器返回并将对应的IO事件分发到对应的处理器(即回调函数)中,最后处理器调用read/write函数来进行IO操作。

异步模型的特点是性能和可伸缩性比同步模型要好很多,但是其结构复杂,不易于编写和维护。在异步模型中,IO之前的代码(IO任务的提交者)和IO之后的处理代码(回调函数)是割裂开来的。

协程与网络编程

协程为克服同步模型和异步模型的缺点,并结合他们的优点提供了可能: 现在假设我们有3个协程A,B,C分别要进行数次IO操作。这3个协程运行在同一个调度器或者说线程的上下文中,并依次使用CPU。调度器在其内部维护了一个多路复用器(epoll/select/poll)。

协程A首先运行,当它执行到一个IO操作,但该IO操作并没有立即就绪时,A将该IO事件注册到调度器中,并主动放弃CPU。这时调度器将B切换到CPU上开始执行,同样,当它碰到一个IO操作的时候将IO事件注册到调度器中,并主动放弃CPU。调度器将C切换到cpu上开始执行。当所有协程都被“阻塞”后,调度器检查注册的IO事件是否发生或就绪。假设此时协程B注册的IO时间已经就绪,调度器将恢复B的执行,B将从上次放弃CPU的地方接着向下运行。A和C同理。

这样,对于每一个协程来说,是同步的模型;但是对于整个应用程序来说,却是异步的模型。

好了,原理说完了,我们来看一个实际的例子,echo server。

echo server

在这个例子中,我们将使用orchid库来编写一个echo server。orchid库是一个构建于boost基础上的 协程/网络IO 库。

echo server首先必须要处理连接事件,我们创建一个协程来专门处理连接事件:

typedef boost::shared_ptr<orchid::socket> socket_ptr; 
//处理ACCEPT事件的协程
void handle_accept(orchid::coroutine_handle co) {
try {
         orchid::acceptor acceptor(co -> get_scheduler().get_io_service());//构建一个acceptor
acceptor.bind_and_listen("5678",true);
         for(;;) {
            socket_ptr sock(new orchid::socket(co -> get_scheduler().get_io_service()));
acceptor.accept(*sock,co);
            //在调度器上创建一个协程来服务新的socket。第一个参数是要创建的协程的main函数,第二个参数是要创建的协程的栈的大小。
            co -> get_scheduler().spawn(boost::bind(handle_io,_1,sock),orchid::minimum_stack_size());
}
} catch(boost::system::system_error& e) {
            cerr<<e.code()<<" "<<e.what()<<endl;
}
}

在orchid中,协程的main函数必须满足函数签名void(orchid::coroutine_handle),如handle_accept所示,其中参数co是协程句柄,代表了当前函数所位于的协程。

在上面的代码中,我们创建了一个acceptor,并让它监听5678端口,然后在"阻塞"等待连接到来,当连接事件到来时,创建一个新的协程来服务新的socket。处理套接字IO的协程如下:

//处理SOCKET IO事件的协程 
void handle_io(orchid::coroutine_handle co,socket_ptr sock) {
   orchid::tcp_ostream out(*sock,co);
   orchid::tcp_istream in(*sock,co);
   for(std::string str;std::getline(in, str) && out;) {
      out<<str<<endl;
   }
}

IO处理协程首先在传入的套接字上创建了一个输入流和一个输出流,分别代表了TCP的输入和输出。然后不断地从输入流中读取一行,并输出到输出流当中。当socket上的TCP连接断开时,输入流和输出流的eof标志为会被置位,因此循环结束,协程退出。

orchid可以使用户以流的形式来操作套接字。输入流和输出流分别提供了std::istream和std::ostream的接口;输入流和输出流是带缓冲的,如果用户需要无缓冲的读写socket或者自建缓冲,可以直接调用orchid::socket的read和write函数。但是需要注意这两个函数会抛出boost::system_error异常来表示错误。

细心的读者可能已经发现,handle_io的函数签名并不满足void(orchid::coroutine_handle),回到handle_accept中,可以发现,实际上我们使用了boost.bind对handle _ io函数进行了适配,使之符合函数签名的要求。

最后是main函数:

int main() {     
   orchid::scheduler sche;
   sche.spawn(handle_accept,orchid::coroutine::minimum_stack_size());//创建协程
   sche.run();
}

在上面这个echo server的例子中,我们采用了一种 coroutine per connection 的编程模型,与传统的 thread per connection 模型一样的简洁清晰,但是整个程序实际上运行在同一线程当中。

由于协程的切换开销远远小于线程,因此我们可以轻易的同时启动上千协程来同时服务上千连接,这是 thread per connection的模型很难做到的;在性能方面,整个底层的IO系统实际上是使用boost.asio这种高性能的异步io库实现的。而且与IO所费的时间相比,协程切换的开销基本可以忽略。

因此通过协程,我们可以在保持同步IO模型简洁性的同时,获得近似于异步IO模型的高性能。

posted @ 2013-01-01 13:14 江浸月 阅读(6031) | 评论 (5)编辑 收藏
  2011年11月28日

转载请注明出处。谢谢

C++11中有很多激动人心的特性,但是相应的使得C++更加复杂。。。
新标准还修改了原有标准库,并增加了很多内容。

在学习新标准的过程中动手写了个 为std::tuple增加格式化/序列化能力的一小段代码

#define DECLARE_TUPLE_SERIALIZATION_FUNCTION(FUNC_NAME,BEG,SEP,END)     \
namespace sjdfsjfyttsaihfah6755jsdf554433356sdf{                        \
template 
<typename Tuple,std::size_t N>                                 \
struct tuple_printer                                                    \
{                                                                       \
    
static void print(std::ostream& os,const Tuple& t)                  \
    {                                                                   \
        os
<<std::get<std::tuple_size<Tuple>::value - N >(t)<<SEP;       \
        tuple_printer
<Tuple,N-1>::print(os,t);                          \
    }                                                                   \
};                                                                      \
                                                                        \
template 
<typename Tuple>                                               \
struct tuple_printer<Tuple,1>                                           \
{                                                                       \
    
static void print(std::ostream& os,const Tuple& t)                  \
    {                                                                   \
        os
<<std::get<std::tuple_size<Tuple>::value-1>(t);               \
    }                                                                   \
};                                                                      \
}                                                                       \
template 
<typename Tuple>                                               \
void FUNC_NAME(std::ostream& os,const Tuple& t)                         \
{                                                                       \
    os
<<BEG;                                                            \
    sjdfsjfyttsaihfah6755jsdf554433356sdf::tuple_printer
<Tuple,std::tuple_size<Tuple>::value>::print(os,t);    \
    os
<<END;                                                            \
}                                                                       
实现成宏是为了使用起来更方便,可以随意指定 函数名 前缀 分隔符 和 后缀。
使用方法如下:

DECLARE_TUPLE_SERIALIZATION_FUNCTION(serialize_tuple,"<"," , ",">")

int main()
{
    
int i=10;
    auto a 
= std::make_tuple(3,"lala",i,'c');
    serialize_tuple(std::cout,a); 
}

输出为:
<3 , "lala" , 10 , c>

测试环境为GCC 4.5,注意编译时候请打开C++0X支持。


posted @ 2011-11-28 05:17 江浸月 阅读(1995) | 评论 (0)编辑 收藏
  2011年8月13日
题目二:
   题目我做了下改变,使用了上篇文章中提到的那个类X,代码如下:

 1 class X
 2 {
 3 public:
 4     X(){cout<<"default construct"<<endl;}
 5     X(int a):i(a){ cout<<"construct "<<i<<endl;}
 6     ~X(){ cout<<"desconstruct "<<i<<endl;}
 7     X(const X& x):i(x.i)
 8     {
 9         cout<<"copy construct "<<i<<endl;
10     }
11     X& operator++()
12     {
13         cout<<"operator ++(pre) "<<i<<endl;
14         ++i;
15         return *this;
16     }
17     const X operator++(int)
18     {
19         cout<<"operator ++(post) "<<i<<endl;
20         X x(*this);
21         ++i;
22         return x;
23     }
24     X& operator=(int m)
25     {
26         cout<<"operator =(int)"<<endl;
27         i = m;
28         return *this;
29     }
30     X& operator=(const X& x)
31     {
32         cout<<"operator =(X)"<<endl;
33         i=x.i;
34         return *this;
35     }
36     /////////////////////////
37     friend ostream& operator<<(ostream& os,const X& x)
38     {
39         os<<x.i;
40         return os;
41     }
42     friend X operator+(const X& a,const X& b)
43     {
44         cout<<"operator +"<<endl;
45         return X(a.i+b.i);
46     }
47     //////////////////////////
48 public:
49     int i;
50 };

请问以下代码的输出是什么?

1 X a(10),b(20);
2 X c=a+b;

我们来看一下使用GCC4.5(默认编译选项)以及MSVC9.0(BOTH DEBUG AND RELEASE)编译后的实际运行结果:
construct 10
construct 20
operator +
construct 30
desconstruct 30
desconstruct 20
desconstruct 10

简单分析下这个输出:

construct 10 
construct 20 //对应 X a(10),b(20);
operator +  //调用“+”操作符
construct 30 //调用X(int){...},44行处
desconstruct 30 //变量c 的析构
desconstruct 20 //变量b 的析构
desconstruct 10 //变量a 的析构
 从结果可以看出,整个执行过程中没有输出“operator=”,说明压根没有调用“=”操作符,而且整个过程比我想象的要简洁高效,没有临时对象,没有拷贝构造。
结果为什么会是这样呢?这主要归功于编译器的返回值优化的能力。
有关返回值优化的知识,限于篇幅我就不仔细介绍了,但是需要特别指出的是MSVC9.0只在RELEASE模式下默认开启NRVO,即对具名对象的返回值优化,以及返回值优化里面的一个重要的细节,体现在本例里就是:为什么中整个输出中没有出现"opeartor=",即为什么没调用"="操作符。

现在我们将代码稍微改变一下,改成下面的样子:

X a(10),b(20),c;
c
=a+b;  //这里我们将c的构造和赋值分开了

执行的结果如下:

construct 10 //构造a
construct 20 //构造b
default construct //构造 c
operator +  //调用“+”操作符
construct 30 //调用X(int){...},44行处
operator =(X) //调用“=”操作符
desconstruct 30 //代码45行所建立的临时对象的析构
desconstruct 30 //变量c的析构
desconstruct 20 //变量b的析构
desconstruct 10 //变量c的析构

对比前后的输出结果,可以发现多出以下三行
default construct 
operator =(X) 
desconstruct 30 
出现这种差异的原因在于:
定义c的时候会调用默认的构造函数进行初始化,因此第一条语句执行完之后,c已经是一个存在的对象,所以第二条语句并没有权利去直接修改c的内容,必须要通过调用赋值操作符”=“,因此必须要产生一个临时对象。而在第一个例子中,因为执行到第二条语句之前c并没有被创建,所以编译器可以将 表达式a+b的返回值直接构建在c的内存中,从而优化掉临时对象和对“=”的调用。
posted @ 2011-08-13 21:38 江浸月 阅读(2082) | 评论 (7)编辑 收藏
今年要开始找工作了,本着积累经验的目的,跑去做了下MTK的笔试题,笔试的内容主要是C++。
因为开发中一直使用C++,而且对C++里的高级特性:面向对象,模板等都比较熟悉,还没事喜欢研究下STL,BOOST,所以对自己的C++水平比较自信,因此事先也没做任何准备,就直接去笔试了。本来笔试完了后觉得题目蛮简单的,但是本着认真学习的态度回来后把题目都上机试验了下,结果一下就悲剧了,错的体无完服啊。。。
总结了一下:
   1。认真对待,不要小看了笔试题目:做题的时候心想这些笔试题目都很简单啊,很多题目都是扫了一眼就立即写出了答案,结果回来后才发现这些题目都设置了陷阱,让你掉进去就出不来了。
   2。C++基础不够扎实。枉我还一天到晚的研究C++的高级特性,结果很多基础的知识却都是一知半解。
特将此次笔试的一些心得和体会记录于此,好提醒自己。下面主要分析几个我做错的题目。题目并非与原题完全一致。
题目一:
int a=10,b=6;
cout
<<a+b<<" "<<a++<<" "<<b++

请说出上述语句的执行结果。
很多人看过这段代码后估计都会直接就写上了 16 10 6 这样的结果吧,但上机实验的输出结果是: 18 10 6
为什么会出现这样的结果,下面是我的分析过程,如果有不对的地方请大家指正。
为了跟踪代码的执行步骤,我设计了一个类X,这个类是对int的模拟,行为方面与int基本一致,除了会打印出一些帮助我们理解的信息,代码如下:

class X
{
public:
    X(){cout
<<"default construct"<<endl;}
    X(
int a):i(a){ cout<<"construct "<<i<<endl;}
    
~X(){ cout<<"desconstruct "<<i<<endl;}
    X(
const X& x):i(x.i)
    {
        cout
<<"copy construct "<<i<<endl;
    }
    X
& operator++()
    {
        cout
<<"operator ++(pre) "<<i<<endl;
        
++i;
        
return *this;
    }
    
const X operator++(int)
    {
        cout
<<"operator ++(post) "<<i<<endl;
        X x(
*this);
        
++i;
        
return x;
    }
    X
& operator=(int m)
    {
        cout
<<"operator =(int)"<<endl;
        i 
= m;
        
return *this;
    }
    X
& operator=(const X& x)
    {
        cout
<<"operator =(X)"<<endl;
        i
=x.i;
        
return *this;
    }
    
/////////////////////////
    friend ostream& operator<<(ostream& os,const X& x)
    {
        os
<<x.i;
        
return os;
    }
    friend X 
operator+(const X& a,const X& b)
    {
        cout
<<"operator +"<<endl;
        return X(a.i+b.i);
    }
    
//////////////////////////
public:
    
int i;
};

然后执行以下代码:

    X a(10),b(6);
    cout
<<"sum:" <<a+b<<" a:"<<a++<<" b:"<<b++<<endl;

使用GCC4。5编译后,代码的执行结果如下:

construct 10
construct 6
operator ++(post) 6
copy construct 6
operator ++(post) 10
copy construct 10
operator +
construct 18
sum:18 a:10 b:6
desconstruct 18
desconstruct 10
desconstruct 6
desconstruct 7
desconstruct 11
我们来简单分析下这个执行过程:

construct 10
construct 6  //这两行输出对应于 X a(10),b(6); 

operator ++(post) 6
copy construct 6 //表明首先执行了  cout<<"sum:" <<a+b<<" a:"<<a++<<" b:"<<b++<<endl;这句中的 b++这个表达式,
                              b++这个表达式返回了一个值为6的临时对象,而b本身则变成了7。
operator ++(post) 10
copy construct 10  //这句的分析同上

operator +
construct 18 //对应于表达式 a+b ,可以看到,此时的a和b已经变成了11和7。表达式返回了一个值为18的临时对象。

sum:18 a:10 b:6 //输出的结果,从结果可以看出,实际上打印出的值分别为 a+b,a++和b++三个表达式所返回的临时变量。

desconstruct 18 //a+b 表达式返回的临时变量的析构
desconstruct 10 //a++ 表达式返回的临时变量的析构
desconstruct 6 //b++表达式返回的临时变量的析构
desconstruct 7 //变量a 的析构
desconstruct 11  //变量b的析构

真相大白了。为什么编译器会这样来编译这个表达式呢?
下面2楼的夜风同学给出了正确答案。。为了不误导后面的同学,特此编辑掉。。

上述实验的环境均为GCC4。5  据同学说VS2010执行的结果在DEBUG下和RELEASE下居然分别为:16 10 6 和18 10 6,不过我没有去验证过,有兴趣的同学可以去验证并分析一下。
做这样一道题还是让我收获很多,巩固了C++的基础。
今天就写道这里,后面有时间会陆续放出对其他“陷阱”题目的分析。
(未完待续)

posted @ 2011-08-13 17:30 江浸月 阅读(3203) | 评论 (19)编辑 收藏
  2011年5月26日
首先,BOOST中有4种有关互斥量得概念。
1.LOCKABLE :仅支持排它型所有权
2.TIMEDLOCKABLE:支持带超时的排它型所有权
3.SHAREDLOCKABLE: 支持带超时的排他型所有权和共享型所有权(读写锁)
4.UPGRADELOCKABLE: 
支持带超时的排他型所有权和共享型所有权,以及共享型所有权升级为排他型所有权(升级过程阻塞)(也支持降级)

可以看到2强化自1,3强化自2.4强化自3,支持某一概念则一定支持其强化自的概念。

boost::mutex 实现了LOCKABLE概念 (boost::recursive_mutex 是其递归锁的版本)
boost::timed_mutex 实现了TIMEDLOCKABLE概念 
(boost::recursive_timed_mutex 是其递归锁的版本)
boost::shared_mutex实现了SHAREDLOCKABLE概念
boost::shared_mutex同样实现了UPGRADELOCKABLE概念

出于提供RAII操作风格和安全等其他一些原因BOOST不希望用户直接调用各种MUTEX类型中的相关接口,而是通过它提供的一些LOCK_TYPE来帮助我们调用。

主要的LOCK_TYPE包括:

boost::unique_lock<LOCKABLE> 针对支持LOCKABLE概念的类型(上述4中MUTEX类型都支持LOCKABLE概念)。以RAII的方式调用该类的lock() 
(调用成功后排它的独占该互斥量)和 unlock() 方法。

boost::shared_lock<SHAREDLOCKABLE>针对支持SHAREDLOCKABLE概念的类型,boost::shared_mutex实现了该概念,注意,支持SHAREDLOCKABLE概念的类既支持排他的独占(写锁,通过调用lock unlock系列函数),也支持共享的方式占用(读锁,通过调用lock_shared系列),
shared_lock默认调用
lock_shared系列。

最主要最常用的就是上面这两个LOCK类型,分别代表独占方式和共享方式,其他的就不一一分析了。

下面是个从http://hi.baidu.com/jrckkyy/blog/item/d7ccb508dfba2e3ce8248817.html此处找到的例子

typedef boost::shared_mutex rwmutex; 
typedef boost::shared_lock<rwmutex> readLock; 
typedef boost::uniq_lock<rwmutex> writeLock; 

rwmutex  _rwmutex; 

void readOnly() 

... 
{ // 临界区 
readLock rdlock
(_rwmutex)
... 
do something 
... 

... 


void writeOnly() 

... 
{ // 临界区 
writeLock wlock(
_rwmutex); 
... 
do something 
... 

... 







posted @ 2011-05-26 01:10 江浸月 阅读(3913) | 评论 (0)编辑 收藏
仅列出标题