martin

thinking

常用链接

统计

software

最新评论

2013年7月8日 #

Linux操作系统中中断上下文中的互斥

在Linux系统中,有两种不同的上下文:进程上下文、中断上下文。
在中断中没有进程上下文,而具有中断上下文,因此在中断上下文中不能发生睡眠,也就是不能发生进程切换。
这就决定了在在中断上下文中不能采用同步原语(信号量,管程、同步变量等)和长期互斥原语(这会导致进程睡眠), 而只能采用短期互斥原语(例如自旋锁)。

曾经,中断处理程序并不具有自己的栈。相反,它们共享所中断进程的内核栈。内核栈的大小是两页,具体地说,在32位体系结构上是8KB,在64位体系结构上是16KB.
现在。中断处理程序有了自己的栈,每个处理器一个,大小为一页。这个栈就称为中断栈,尽管中断栈的大小是原先共享栈的一半,但平均可用栈空间大得多,因为中断处理
程序把这一整页占为己有。

UP(单CPU系统)上的中断处理
互斥
如果一个中断处理程序的代码访问或者更新了由非中断的代码(通常称为基准代码)使用的同一数据结构,那么就会出现竞争条件。
幸运的是,得到允许的以内核态执行的进程会临时禁止中断。因此,只要基准代码要更新一个与中断处理程序共享的数据结构,那么就
首先禁止中断,执行临界段,然后再重新允许中断。禁止和允许中断的动作就实现了互斥。
在采取中断互斥时,必须使用函数显示地把编码写入算法中。

MP(多CPU系统)上
在MP系统上,中断可以在任何处理器上出现。从最低限度上来说,每个进程会接收时钟中断,但也可能接收I/O中断。在MP系统上,例程
SPL(禁止中断)所提供的保护并不充分,因为它们执行影响执行它们的处理器上的中断优先级。中断可能会在另一个处理器上出现,如果设备驱动程序
正在别处运行,那么会造成一个竞争条件。因为中断处理程序代表另一个进入内核的入口点。

当基准驱动程序代码和中断处理程序之间共享数据结构时,UP可以通过屏蔽中断来防止出现竞争条件的技术,在多线程MP内核中还不充分。
临界段要在一个处理器上执行,执行屏蔽中断例程只会屏蔽在那个处理器上出现的中断。如果在别的处理器上出现中断,那么立即就会有
两个处理器同时访问、而且可能更新临界资源。既然这些临界段需要短期互斥,那么可以使用自旋锁来对数据进行保护。

如果不考虑中断处理程序和基准代码之间的互斥,则Linux中的中断处理程序是无须重入的。当一个给定的中断处理程序正在执行时,相应的中断线
在所有处理器上都会被屏蔽掉,以防止同一中断线上接收另一个新的中断。通常情况下,所有的其他中断都是打开的,所以这些不同中断线上的其他中断
都能处理,但当前中断线总是被禁止的。由此可以看出,同一个中断处理程序绝不会被同时调用以处理嵌套的中断。这极大地简化了中断程序的编写。

posted @ 2013-07-08 14:01 martin_yahoo 阅读(3354) | 评论 (0)编辑 收藏

2013年6月20日 #

unix 类系统中的进程同步方式代码示例

MP(多CPU)同步原语代码示例
                   ----引自《现代体系结构上的UNIX系统》

为了便于对示例的展开,我们首先假定一个场景:
内核中把检测到的错误信息记录到一个磁盘文件中的情形。出错信息通过内存中的一个队列来传递给日志进程(logging process)的。
当出现一个错误时,就在队列中加入一项,并且通过调用函数log_error 通知日志进程。出错日志进程接着把队列中的项写到磁盘上。
这就使得碰到错误的进程不必等候I/O完成或者或得为了向文件执行I/O而可能需要的任何锁,并且避免了任何可能的上锁次序问题。

代码1, 采用事件计数的出错日志通知机制

                                                日志进程
log_error(error)                    |
{                                         |       for(next=1; ; next++) {
    lock(&err_queue);             |           await(&err_event, next);
    把出错信息加入到队列           |          lock(&err_queue);
    unlock(&err_queue);          |          从队列中删除项
    advance(&err_event);        |          unlock(&err_queue);
}                                         |          write error to disk
                                           |       }

队列本身由一个自旋锁来保护。在本例中,事件计数只用于同步的目的,并不提供互斥。
在试用事件计数的时候,advance操作会永久性地改变事件计数的状态。advance和await操作的相对时序没有关系。

代码2, 采用同步变量的出错日志通知机制
                                                 日志进程
log_error(error)                            |
{                                                 |        for(;;){
   lock(&err_queue);                      |              lock(&err_queue);
   把出错信息加入到队列                    |              if (queue_empty){
   SV_SIGNAL(&err_syncvar, 0);      |                  SV_WAIT(&err_syncvar, PRI, &err_queue);
   unlock(&err_queue);                   |                  lock(&err_queue);
}                                                 |              }
                                                   |              从队列中删除项
                                                   |              unlock(&err_queue);
                                                   |              把错误写入磁盘
                                                   |        }

因为同步变量自身没有保留状态,所以当日志进程测试队列的状态并决定是等待一项还是从队列中删除一项的时候,必须占有自旋锁。类似地,log_error在
发送信号时也必须占有自旋锁。注,SV_WAIT将释放自旋锁,并且阻塞日志进程,SV_SIGNAL到后从阻塞处继续执行。

代码3, 采用管程的出错日志通知机制
                                                                 日志进程
log_error(error)                                  |       for(;;){
{                                                       |           mon_enter(&err_mon);
   mon_enter(&err_mon);                     |           if (queue empty)
   把出错信息加入到队列                          |               mon_wait(&err_mon, NEWENTRY);
                                                         |
   mon_signal(&err_mon, NEWENTRY);  |           从队列中删除项
   mon_exit(&err_mon);                       |           mon_exit(&err_mon);
}                                                       |           把错误写入磁盘
                                                         |       }
                                    
代码4, 采用信号量的出错日志通知机制
                                                日志进程
log_error(error)                      |       for(;;){
{                                          |           P(&err_sema);
    lock(&err_queue);               |           lock(&err_queue);
    把出错信息加入到队列            |           从队列中删除项
    unlock(err_queue);              |           unlock(&err_queue);
    V(&err_sema);                   |           把错误写入磁盘
}                                          |       }

posted @ 2013-06-20 15:02 martin_yahoo 阅读(1526) | 评论 (0)编辑 收藏

unix 类系统中的进程同步方式总结

我们把异步环境下的一组并发进程因直接制约而互相发送消息、进行互相合作、互相等待,使得各进程按一定的速度执行的过程称为进程间的同步。
具有同步关系的一组并发进程称为合作进程,合作进程间互相发送的信号称为消息或事件。 如果我们对一个消息或事件赋以唯一的消息名,则我们
可用过程 wait (消息名)  表示进程等待合作进程发来的消息,而用过程 signal (消息名) 表示向合作进程发送消息。
(引自百度百科)

进程间的同步方式:
为了实现进程互斥地进入自己的临界区,操作系统中设置专门的同步机制来协调各进程间的运行。所有的同步机制都应遵循下书四条准则:
1)空闲让进
2)忙则等待
3)有限等待
4)让权原则。当进程不能进入自己的临界区时,应立即释放处理机,以免进程陷入“忙等”状态。

1.单CPU (UP)机器利用sleep/wakeup函数对实现同步机制。
函数sleep是一个内部的内核例程,它挂起调用它的进程,直到指定的事件发生为止。这是一个以内核态运行的进程自愿出让控制权,允许自己被抢占。
函数wakeup用于发出一个特定事件已经出现的信号,它使得所有等待该事件的进程被唤醒,并放回到运行队列中。事件用一个整数值来表示,它往往
是该事件相关的内核数据结构的地址。

void lock_object( char *flag_ptr)
{
    lock(&object_locking);        //自旋锁
     while (*flag_ptr)
           sleep(flag_ptr);
     *flag_ptr = 1;
    unlock(&object_locking);
}

void unlock_object( char *flag_ptr)
{
    lock( &object_locking );
    *flag_ptr = 0;
    wakeup( flag_ptr);
    unlock( &object_locking );
}

应为wakeup操作没有记忆,所以wakeup函数必须唤醒在同一事件上睡眠的所有进程。在多CPU系统上,即MP上sleep/wakeup机制不起作用。

2.SVR4.2 MP 提供了单独的执行进程同步的原语:同步变量。
因为同步变量不包含状态,所以可以把它们想成是sleep/wakeup的一种MP变形。相反,所需的任何状态信息都保存在外部标志或者计数器中。
同步变量的设计要同自旋锁配合工作。

同步变量被声明为sv_t类型,采用下面的函数可以给它分配空间和进行初始化:
sv_t *SV_ALLOC( int slpflag);
slpflag指定,如果需要为同步变量分配内存,那么是否能阻塞进程。

回收同步变量可以调用
void SV_DEALLOC( sv_t *svp );

内核希望单独等候的每一个事件都用一个不同的同步变量来表示,这就好比配合sleep如何使用唯一的事件参数。
void SV_WAIT( sv_t *svp, int pri, lock_t *lockp );

要触发在同步变量上的事件,可以使用下面的函数:
void SV_SIGNAL( sv_t *svp, int flags);
SV_SIGNAL与wakeup的相似之处在于,如果没有正在睡眠的进程,那么就对过去曾经执行过的操作没有记忆,调用什么也不做。
SV_SIGNAL只唤醒一个进程。如果要唤醒在事件上睡眠的所有进程,可以用同步变量的下列函数来实现:
void SV_BROADCAST( sv_t *svp, int flags);

如果在事件被触发之前出现了一个UNIX信号,那么下面的SV_WAIT变形会唤醒进程:
bool_t SV_WAIT_SIG( sv_t *svp, int pri, lock_t *lkp );
返回的代码表明发生了什么样的事件:如果出现了一个UNIX信号,那么它返回FALSE,如果出现了SV_SIGNAL或SV_BROADCAST,那么它返回TRUE.

3.采用信号量的同步
将信号量的值初始化为0,就可以用于进程同步,这样允许通过使用P操作让一个进程等待某个事件发生。既然信号量被初始化为0,那么进程将立即阻塞。
另一个进程使用V操作能够发出信号,表明事件已经结束。V操作导致正等待事件的进程被唤醒,并继续进行。因为即使在信号量上没有阻塞进程,
V操作也会给信号量加1,所以在前一个进程能够执行P操作之前出发事件会导致进程继续进行,不必等待。这是一种受欢迎的情形,因为它不需要额外的
协调工作,就能够处理在等候事件的进程同发信号表明该事件完成的进程之间本来就有的竞争条件。

进程1                                        进程2
p(s)       /*等待事件*/                    .
                                                  .
                                                  .
                                                V(s) /*触发事件*/


4.利用管程进行同步
管程为临界资源以及访问或者修改该资源的所有临界段提供了互斥机制,它还提供了在使用管程的诸进程之间进行同步的手段。一个管程可以想成是一个装有
资源的隔间。进程要访问资源,它必须首先进入隔间。通过一次只允许一个进程进入隔间,就做到了互斥。如果在管程已经投入使用的时候,别的进程试图进
入它,那就会被阻塞,直到使用管程的进程退出管程为止,或者在与管程关联的事件上等待。每个管程都可能有一个或者更多的事件,若干进程能够在这些事
件上等待。进程被阻塞这些事件上,直到在管程内执行的其他进程触发事件为止。根据定义,触发操作只能从管程内部完成。


5.利用事件计数进行同步
事件计数是一个非递减的正整数,在这个数值上定义了3种操作。操作advance(E)将事件计数E加1,这叫做出发事件。
操作await(E,V)致使调用进程被阻塞,指导事件计数E的值达到V为止。如果在调用await的时候,事件计数的值大于或等于V,那么进程继续执行,而不会阻塞,
因为事件是以前触发的。事件计数的当前值可以用read(E)来读取。在创建事件计数的时候,它被初始化为0,而且在数值上永远不会减少。假定保存事件计数值
得存储器位置足够大,于是事件计数在其整个生命期中,一直都不会溢出(通常一个32位的无符号整数就够了)。

有关代码示例,请参见后面的随笔。

posted @ 2013-06-20 13:49 martin_yahoo 阅读(2081) | 评论 (2)编辑 收藏

2013年6月8日 #

MP操作系统内核中自旋锁(spinlock)的实现

在多CPU系统,即MP系统中,存在总线仲裁。

1. 原子操作
从CPU或者I/O设备到主存储器的单次读或者写操作为原子操作。
这样的操作一旦开始,就不能被系统上来自CPU或者I/O设备的任何其他存储操作所中断,或者受到他们的干扰。

原子变量

2. 自旋锁 (自旋锁用于短期互斥)
自旋锁得名于这样一个事实,一个进程在等候另一个进程正在使用的锁时会处于忙等待(busy-wait,在一个循环中自旋)状态。
typedef int lock_t;
void initlock( volatile lock_t * lock_status)
{
   *lock_status = 0;
}

int
test_and_set(volatile int *addr)
{
     int old_value;
     old_value = swap_atomic(addr, 1);
     if (old_value == 0)
          return 0;
     return 1;
}

void lock(volatile lock_t *lock_status)
{
    while (test_and_set( lock_status) == 1)            //被锁定时,进程在此自旋。
       ;
 }
// test_and_set 如果前面的状态不为0就返回1,否则返回0.
//如果锁的状态已经是1(锁已经被占用),那么test_and_set函数返回1,并且处理器在循环中自旋,直到该锁被释放为止。只要把锁的状态设置为0,就可以释放锁了。

void
unlock(volatile lock_t * lock_status)
{
   *lock_status =0;
}

减少对锁的争用可以采用两种办法:
第一、内核针对不同的临界资源使用不同的自旋锁,防止处理器在没有竞争条件威胁的时候被另一个处理器挂起。
第二、增强lock和unlock函数,在上锁的时候屏蔽中断。

posted @ 2013-06-08 17:04 martin_yahoo 阅读(1713) | 评论 (1)编辑 收藏

如何避免在应用中可能出现的死锁

在平时的应用设计中,由于出现多线程(进程)的并发应用,以及分布式应用,比较容易出现死锁现象。

下面来看一个简单的实例:
有两个独立的链表,假定使用两个独立的锁来保护他们,以便能够独立的访问。再假定某个操作要求遍历一个列表所检索到的元素必须从两个列表中断开连接,
而且必须以一次原子操作来完成。

线程1                                                   |           线程2
lock(&lock_a);                                      |        lock(&lock_b);
find element to unlink on list a               |        find element to unlink on list b
lock(&lock_b);                                      |        lock(&lock_a);
unlink element from both lists                |        unlink element from both lists
unlock(&lock_b);                                   |        unlock(&lock_a);
unlock(&lock_a);                                   |        unlock(&lock_b);

可能出现死锁的情况。
这要求在消除一个元素是必须同时拥有两个列表的锁。对第一个线程要先或得锁lock_a, 然后要或得锁lock_b.
第2个线程正好相反,它先或得锁lock_b,然后再获取锁lock_a.
如果某一时刻,第一个线程执行到了 find element to unlink on list a, 而第二个线程此时执行到了find element to unlink on list b, 则这两个线程将发生死锁。
这种死锁称为AB-BA死锁。   (注,死锁的发生是跟两个线程的执行时序相关的,例如,第一个线程执行完了所有的这部分代码,线程2才开始执行此段代码,则不会发生死锁。)

如果某个锁不是递归锁,例如lock_a, 而线程1在应用中对它进行多个调用,而没有调用解锁操作,也会发生死锁。
代码示例如下:
lock(&lock_a);
other logic code
lock(&lock_a);
other logic code
unlock(&lock_a);
unlock(&lock_a);

防止死锁的办法: 为了防止发生这类死锁,所有线程必须以相同的次序获得嵌套锁,即以相同的次序获得且同时占有锁。
可以把上面的代码改成如下代码来避免死锁:
lock(&lock_a);
lock(&lock_b);
find element to unlink on list a or b
unlink element from both lists
unlock(&lock_b);
unlock(&lock_a);

当涉及到3个或者更多锁的时候也是如此:只要各线程在获得和释放锁的时候保持相同的次序,那么就不会出现死锁。

posted @ 2013-06-08 14:54 martin_yahoo 阅读(1887) | 评论 (2)编辑 收藏

2009年3月20日 #

应用的扩展

实体(语言内建的数据类型,开发者定义的类和方法等)的定义与声明,实体和指针跟程序设计带来了不同影响.
对于实体或定义,编译器要知道实体真实的物理内存布局,因此让编译器知道这些信息,并且在程序编译完毕后不能更改.要想更改必须重新编译程序.因此如果在系统设计者程序库中运用了inline函数,并且如果应用开发者在应用中用了这个inline函数,则当后来要对inline进行修改时,有可能要导致应用被重新编译.
对于指针,它的大小在特定的机器上是固定的(在32位机器上,它的大小是32位;在64位机器上,它的大小是64位).因此可以改变它的值,而不需要重新编译应用,就可以改变应用的功能.

在面向对象中,可以通过虚函数指针来延迟特定函数的决策,即调用子类的函数.
在C语言中,我们可以通过函数指针来对函数的功能进行推迟决策.
在C++中,我们也可以通过函数指针(函数对象)、对象指针来推迟决策,从而使程序的功能更有弹性。例如,在设计模式中的strategy模式中,就是通过在contex中包含一个指向strategy的指针来实现的。我们可以定义一个抽象的strategy接口,然后由各个具体的strategy实现这些接口,从而在保证应用架构几乎不做任何调整下,实现不同的功能。当然在这种实现方式中,我们应该加入strategy的决议者,由它来裁决采用哪一种策略方式。决议者可以采用配置文件、应用的输入等作为决议的依据。

熟悉symbian的人,很快就会发现:它与symbian中的ECOM架构很相似。它要求各种strategy的实现方式被包含在共享的DLL中,并由RLibrary::Loard()调用动态载入.
使用定义抽象接口,然后在各DLL中定义具体的实现,并且动态载入,我们可以比较容易地实现所谓的插件(plugin)。插件的载入取决于配置,或相应的输入检测。

下面给出在linux和windows上从动态库中查找和载入的例子:
#ifdef WIN32
        HINSTANCE hDll;
        if(!(hDll = LoadLibrary(VOCALSIP_DLLPATH)))
        adapter.m_initFunc = (INIT_PROTOSTACK_FUNC)GetProcAddress( hDll, "InitVocalSipStack");
        adapter.m_createFunc = (CREATE_CHANNEL_FUNC)GetProcAddress( hDll, "CreateVocalSipGCChannel");
        adapter.m_cleanupFunc = (CLEANUP_PROTOSTACK_FUNC)GetProcAddress( hDll, "CleanupVocalSipStack");
#else
        void* h_dl = dlopen(VOCALSIP_DLLPATH,RTLD_NOW | RTLD_GLOBAL);
        adapter.m_initFunc = (INIT_PROTOSTACK_FUNC)dlsym( h_dl, "InitVocalSipStack");
        adapter.m_createFunc = (CREATE_CHANNEL_FUNC)dlsym( h_dl, "CreateVocalSipGCChannel");
        adapter.m_cleanupFunc = (CLEANUP_PROTOSTACK_FUNC)dlsym( h_dl, "CleanupVocalSipStack");
#endif

posted @ 2009-03-20 11:41 martin_yahoo 阅读(1263) | 评论 (0)编辑 收藏

2009年3月18日 #

mutex与semaphore的区别

"互斥(mutext)和旗语(semaphore)之间有什么不同?"这样的问题简短而有力,但要回答却相当困难.即使有经验的实时操作系统(RTOS)用户在区别如何正确使用mutex和semaphore时也存在着困难.
但这一点很不幸而且很危险,因为无任这两种原生RTOS中的哪一种被错误使用,都会导致嵌入式系统出现意想不到的错误,特别是这些系统为有关生命安全的产品时.
有关mutex和semaphore的荒诞说法是它们是相似的,甚至是可以互换的.正确的事实是尽管mutex和semaphore在它们的执行上有相似之处,但是我们还是应该在使用它们时加以区别对待.
最普遍(但也是不正确)的答案是:mutex和semphore非常相似,它们只有一个区别,那就是semaphores的计数可以超过1. 差不多所有的工程师都能正确的理解:mutex是一个二进制标志,可以通过它来确保执行流在代码关键区(critical section of code)互相排斥,从而对共享资源加一保护.但当他们被要求进一步回答如何使用"计算方法semaphore"的方式时,大部分工程师的回答就如同教科书书一般的刻板---semaphore用于保护多重同类资源.
通过类比办法,我们很容易解释为什么"多重资源"场景是有缺陷的.如果你认为一个mutex是由操作系统拥有的关键值的话,我们可以很容易地将个别的mutex比喻是城市咖啡店中一间浴室的钥匙.如果你想使用浴室,却找不到钥匙,你就必须在一个队列中等候.同样地,mutex则协串行化多项任务,以取得全域资源的共享,并且为等待队列中的任务分配一个静候其循序渐进的位置.
但这种简单的资源保护协议并不使用于两间相同浴室的情况.如果把一个semaphore概括为一个mutex,使其能保护两个或更多相同的资源,那么在我们的比喻中,它就象是放着两把相同钥匙的蓝子,你可以用任何一把打开任何一扇浴室的门.
因此,semaphore本身并不能解决多个相同资源的问题.咖啡店中的客人可能只知道有一把钥匙,但并不知道哪间浴室可用.如果你试图以此方式使用semaphore,你将会发现需要更多的状态信息---它们通常是由不同的mutex所保护的共享资源.
正确使用semaphore是为了使信号从一项任务传至另一项任务.mutex意味着取得与释放,使用受保护共享资源的每一次任务都是以这样的顺序进行.相比之下,使用semaphore的任务通常不是发送信号,就是进入等待状态,不可能同时发生.
例如,任务1可能包含程序代码,当按下"电源"(power)按钮时,即可提出(如发送信号或增量)一个特别的semaphore; 任务2则依据相同的semaphore而用于唤醒显示器. 在这种情况下,其中一项任务是信号的生产者,另一项任务是信号的消费者.

用一个例子来做总结,首先展示如何使用mutex:
/* Task 1 */
mutexWait(mutex_mens_room);
// Safely use shared resource
mutexRelease(mutex_mens_room);

/* Task 2 */
mutexWait(mutex_mens_room);
// Safely use shared resource
mutexRelease(mutex_mens_room);

相应地,你总是采用下列方法使用semaphore:
/* Task 1 - Producer */
semPost(sem_power_btn); // Send the signal

/* Task 2 - Consumer */
semPend(sem_power_btn); // Wait for signal

重要的是,semaphores可以被interrupt service routine(ISR)中断服务程序用来向task发送信号.发送一个semaphore是一个非阻塞的RTOS行为,并且ISR安全.因为这种技术排除了在task级别的为了是中断不使能而引起的错误的可能性,从ISR中发出信号是一种使嵌入式软件更加可靠的设计方式.

http://www.embedded.com/columns/guest/210605040?printable=true
http://www.eettaiwan.com/ART_8800557420_676964_NT_a22f6436.HTM

posted @ 2009-03-18 15:37 martin_yahoo 阅读(22010) | 评论 (4)编辑 收藏

2009年3月17日 #

应用系统中的定时器设计

     摘要: 前一段时间,读symbian文档资料,和Darwin,ICE等开源代码,经常碰到定时器和定时器事件.故对定时器的实现进行了一些整理,作为读书笔记,以防以后忘记.  阅读全文

posted @ 2009-03-17 17:34 martin_yahoo 阅读(2469) | 评论 (2)编辑 收藏

2009年3月11日 #

线程间数据传递

 

在以前的multi-process程序中,process之间通过共享内存、操作系统提供的消息对列,命名管道等不同方式进行数据传递。为了减少内存,以及进程切换时引发的上下文切换的开销,现在的系统一般采用multi-thread 工作方式。

process都有各自独立的地址空间,并且是专有的,因此一个process不能通过指针访问另一个process中的地址空间。而属于同一process的各thread,它们共享同一process的地址空间,因此一个thread可以通过指针访问另一个thread中的地址。这样我们可以在应用中自己定义消息队列,并对消息队列的访问进行管理,简化程序并提高性能。

 

multi-thread应用中,我们可以使用消息对列来在不同thread之间进行消息传递,并降低各thread之间的藕合。

它们之间的关系如下:

生产者:向消息队列中放数据的线程。

消费者:从消息队列中取出数据的线程。

 
 生产者 ----> 消息队列 ----->消费者

 

如果应用规定消息队列的最大长度。在对队列进行控制时,应该当消息队列满时,可能要让生产者进行等待,直到消息队列中有新的位置可以放入新的消息。当消息队列为空时,应该让消费者进行等待,知道有新的消息被加到消息队列中。

 

在该模型中有两点需要注意:

1.不同生产者,消费者同时并发操作消息队列时,对各操作的串行化。这个问题可以通过对队列的操作进行加锁来实现。它的实现可以参考另一篇随笔《C++同步锁管理的一种方法》。

2.在消息队列为空或满时,应用程序的处理。

这可以在C++中采用Monitor.Monitor中对某种条件进行监控。

Monitor 对象可以采取的动作:

(1)、等待操作 wait(long timeout = INFINITE), 缺省情况下无限等待下去。

(2)、发信号操作 signal(long count = 1), 缺省情况下激活一个正在消息队列上进行等代的线程。

 

对于Monitor的实现,可以很简单地用下列两种方式实现:

1、采用ACE中的ACE_Event, ACE_Thread_Semphore实现。

2、采用 ICECond实现, Linux上,Cond实际上是通过pthread_cond_t来实现的。

 

posted @ 2009-03-11 12:30 martin_yahoo 阅读(3610) | 评论 (0)编辑 收藏

2009年3月9日 #

智能指针的代码实例

前段时间,写了一点关于智能指针的东西,有读者反映没有代码比较难懂.现给出源码,并稍微加以解释.

智能指针类用到的基类的定义:
template<typename T>
class HandleBase
{
public:

    typedef T element_type;

    T* get() const
    {
        return _ptr;
    }
   
   //重载->操作符,返回所指对象的指针.

    T* operator->() const
    {
        if(!_ptr)
        {
            //
            // We don't throw directly NullHandleException here to
            // keep the code size of this method to a minimun (the
            // assembly code for throwing an exception is much bigger
            // than just a function call). This maximises the chances
            // of inlining by compiler optimization.
            //
            throwNullHandleException(__FILE__, __LINE__);
        }

        return _ptr;
    }

//  通过智能指针获取所指对象的引用.

    T& operator*() const
    {
        if(!_ptr)
        {
            //
            // We don't throw directly NullHandleException here to
            // keep the code size of this method to a minimun (the
            // assembly code for throwing an exception is much bigger
            // than just a function call). This maximises the chances
            // of inlining by compiler optimization.
            //
            throwNullHandleException(__FILE__, __LINE__);
        }

        return *_ptr;
    }

    operator bool() const
    {
        return _ptr ? true : false;
    }

    void swap(HandleBase& other)
    {
        std::swap(_ptr, other._ptr);
    }

    T* _ptr;

private:

    void throwNullHandleException(const char *, int) const;
};

......


// 智能指针类定义

template<typename T>
class Handle : public HandleBase<T>
{
public:

    Handle(T* p = 0)               //智能指针的构造函数
    {
        this->_ptr = p;

        if(this->_ptr)
        {
            this->_ptr->__incRef();         //在构造函数中增加所指对象的引用计数
        }
    }

    template<typename Y>                  //拷贝构造函数
    Handle(const Handle<Y>& r)
    {
        this->_ptr = r._ptr;

        if(this->_ptr)
        {
            this->_ptr->__incRef();   //在构造函数中增加所指对象的引用计数
        }
    }

    Handle(const Handle& r)         //拷贝构造函数
    {
        this->_ptr = r._ptr;

        if(this->_ptr)
        {
            this->_ptr->__incRef();    //在构造函数中增加所指对象的引用计数
        }
    }

    ~Handle()
    {
        if(this->_ptr)
        {
            this->_ptr->__decRef();      //在析构函数中减少所指对象的引用计数
        }
    }

// 重载=操作符, 要注意所有权 (即,对原实例的处理).

    Handle& operator=(T* p)         
    {
        if(this->_ptr != p)
        {
            if(p)
            {
                p->__incRef();      //增加新指对象的引用计数
            }

            T* ptr = this->_ptr;
            this->_ptr = p;

            if(ptr)
            {
                ptr->__decRef();   //减少原来所指对象的引用计数
            }
        }
        return *this;
    }

    template<typename Y>
    Handle& operator=(const Handle<Y>& r)
    {
        if(this->_ptr != r._ptr)
        {
            if(r._ptr)
            {
                r._ptr->__incRef();   //增加新指对象的引用计数
            }

            T* ptr = this->_ptr;
            this->_ptr = r._ptr;

            if(ptr)
            {
                ptr->__decRef();      //减少原来所指对象的引用计数
            }
        }
        return *this;
    }

    Handle& operator=(const Handle& r)
    {
        if(this->_ptr != r._ptr)
        {
            if(r._ptr)
            {
                r._ptr->__incRef();            //增加新指对象的引用计数
            }

            T* ptr = this->_ptr;
            this->_ptr = r._ptr;

            if(ptr)
            {
                ptr->__decRef();            //减少原来所指对象的引用计数
            }
        }
        return *this;
    }

 跟智能指针配合使用的对象.要能够跟指针智能配合使用,这些对象应该是从下列类的派生类的实例.
class SimpleShared
{
public:

    SimpleShared();
    SimpleShared(const SimpleShared&);

    virtual ~SimpleShared()
    {
    }

    SimpleShared& operator=(const SimpleShared&)
    {
        return *this;
    }

    void __incRef()
    {
        assert(_ref >= 0);
        ++_ref;
    }

    void __decRef()
    {
        assert(_ref > 0);
        if(--_ref == 0)               // 如果引用计数为0,则摧毁对象本身.
        {
            if(!_noDelete)
            {
                _noDelete = true;
                delete this;
            }
        }
    }

    int __getRef() const
    {
        return _ref;
    }

    void __setNoDelete(bool b)
    {
        _noDelete = b;
    }

private:

    int _ref;
    bool _noDelete;
};

class Shared
{
public:

    Shared();
    Shared(const Shared&);

    virtual ~Shared()
    {
    }

    Shared& operator=(const Shared&)
    {
        return *this;
    }

    virtual void __incRef();
    virtual void __decRef();
    virtual int __getRef() const;
    virtual void __setNoDelete(bool);

protected:

#if defined(_WIN32)
    LONG _ref;
#elif defined(ICE_HAS_ATOMIC_FUNCTIONS)
    volatile int _ref;
#else
    int _ref;
    Mutex _mutex;
#endif
    bool _noDelete;
};

posted @ 2009-03-09 16:07 martin_yahoo 阅读(2130) | 评论 (6)编辑 收藏

仅列出标题  下一页