Chinaunix首页 | 论坛 | 博客
  • 博客访问: 92303
  • 博文数量: 41
  • 博客积分: 1531
  • 博客等级: 上尉
  • 技术积分: 420
  • 用 户 组: 普通用户
  • 注册时间: 2005-05-17 10:15
文章分类

全部博文(41)

文章存档

2014年(2)

2012年(1)

2011年(2)

2008年(9)

2007年(2)

2006年(5)

2005年(20)

分类: C/C++

2005-10-30 18:33:16

本文讲述了一种实现异步存取共享数据的方法,他没有用到操作系统提供的同步互斥借口,也没有用CPU提供的特殊指令,但能供保证读到的共享数据的完整性。

分两部分:

1. 对这种方法的证明

2. C++模板类的实现

1. 证明

template
class CViewdata{
private:
 T d0;     // 只能由写对象更新
 T d1;     // 只能由写对象更新

 volatile int can_use;     // 只能由写对象写
 volatile int read_using;  // 只能由读对象写
 volatile int read_using2; // 只能有读对象写

public:

 bool get( T &d ); // 返回true,取到了数据,只能由读对象调用
 bool put( const T &d ); // 返回true,放入了数据,只能由写对象调用

...

};

读对象通过get取数据,写对象通过put写数据。初始时,d0、d1内都是完整数据。

讲述了两种证明方法,第二较简单:

 // 处理情况:
 //     存在两个对象,读对象和写对象及数据。写对象写数据是需要时间的,在某个时刻,也就是写操作
 //     开始了但还没有完成前的任何时刻,被写数据是不完整的。而要求读对象每次读到的数据是完整。
 // 显然这可以通过操作系统的互斥、同步接口完成,我在这里提出的是另一种方法。
 //
 // 设置can_use,read_using,read_using2的目的是为了保证读对象每次读到的viewdata_t数据是完整的。
 //
 // can_use = 0时,d0为有效,否则为d1有效
 // 初始化该数据时,应该确保d0和d1都是完整的viewdata_t数据,并置can_use=0,read_using=-1,read_using2=-1
 //
 // 写对象:
 //     当需要更改viewdata_t数据时,更改d{!can_use},然后设置can_use=!can_use,那么其他对象通过
 //     can_use读到的viewdata_t数据始终是完整的,更改前提read_using==-1&&read_using2==-1。
 //     分两步,
 //     1:读入read_using和read_using2到R1和R2;当R1和R2的值更改后,该步骤才算完
 //     2:if( R1 != -1 || R2 != -1 ) goto 1 or 4
 //        d{!can_use}=data;
 //        can_use=!can_use;当内存can_use改变时,该步骤才算完成
 //     3:
 // 读对象:
 //     分七步,
 //     1:读入can_use到R;当R的值更改后,该步骤才算完成
 //     2:read_using2=R;当内存read_using2为R时,该步骤才算完成
 //     3:读入can_use到R;当R的值更改后,该步骤才算完成
 //     4:if( read_using2 != R ) goto 1 or 8;
 //        read_using=read_using2;当内存read_using为read_using2的值时,该步骤才算完成
 //     5:读入d{can_use}到局存data;当data的每个子元素都得到更改后,该步骤才算完成
 //     6: read_using2=-1;当内存read_using2变为-1时,该步骤才算完成
 //     7:read_using=-1;当内存read_using变为-1时,该步骤才算完成
 //     8:
 //

证法一:
 // -----|------------------|-----------|-----|------|---------|--|--|----->  时间轴
 //     t1                 t2          t3    t4     t5========t6 t7 t8  
 //
 // -----|------------------|-----------|------------|---------|--|--|----->  写对象
 //  can_use=USE          
 //
 //
 // ------------------------|-----------|-----|------|---------|--|--|----->  读对象
 //                        (1)         (2)   (3)    (4)       (5)(6)(7)       每个步骤完成的时刻
 //                       R=USE
 //
 // 只要can_use的值不在[t5,t6]时间段内翻转,就行了。而读写对象的以上步骤能保证这一点。以下为论证步骤:
 //
 // 前提同一时间内存值唯一,当前情况:can_use=USE后第一次读操作的第一步已完成,显然每种情形都从这种情况开始。
 // 
 // 写对象:
 //     第一步完成在t3之后时,那么这次写操作不会在[t5,t6]段对d{USE}更改(写操作的第二步保证了它)
 //     第一步完成在[t1,t2]段时,
 //         如果第二步完成在t4之后,那么显然在[t5,t6]时间段肯定不会对d{USE}更改
 //         如果第二步完成在[t3,t4]段,那么从第二步完成的时刻->t8时间段的后续写操作是不会对d{USE}更改的(第二步阻止了它)
 //         如果第二步完成在[t2,t3]段,以下讨论第二次写操作的第一步完成在[t2,t3]段(因为当第二次写操作的第一步t3以后,那么第二次写操作不可能继续,写操作的第二步保证了它)
 //             如果第二次写操作的第二步完成发生在[t3,t4]段,那么从第二次操作的第二步完成时刻->t8时间段的后续写操作是不会对d{USE}更改的(因为这次读操作在第三次写操作开始前将被取消,读操作的第四步保证了它)
 //             如果第二次写操作的第二步完成发生在[t2,t3]段,以下讨论第三次写操作,
 //                 第三次写操作的第一步完成发生在[t2,t3]段,那么这次写操作的讨论情形如同第一次写操作的第一步完成在[t2,t3]段的情形。
 //                 第三次写操作的第一步完成发生在t3以后,那么第三次写操作在[t5,t6]段是不可能更改d{USE}的(写操作的第二步保证了它)
 //         如果第二步完成在[t1,t2]段,那么第二次写操作讨论的情形如同第一次写操作
 //     第一步完成在[t2,t3]段时,
 //         如果第二步完成在t4之后,同第一步完成在[t1,t2]段时
 //         如果第二步完成在[t3,t4]段,同第一步完成在[t1,t2]段时
 //         如果第二步完成在[t2,t3]段,同第一步完成在[t1,t2]段时
 //     以上讨论了所有情况,都满足要求
 //
 //
 //

证法二:
 //    以上证明是以读对象为基础讨论写对象,以写对象为基础讨论读对象的证明将更加简单,如下:
 // ---|----------|------|-----------------|-----------|-------------------> 时间轴
 //   t0         t1     t2                t1'          t2'
 //
 // ---|----------|------|-----------------|-----------|-------------------> 写对象
 // can_use=USE  (1)    (2)               (1)         (2)                            
 //                  can_use=!USE                  can_use=USE
 //
 // ---------------------------------------|-----------|-------------------> 读对象
 //                                                                                  
 // 上图显示了读对象的连续两次成功的写操作,要保证读到数据的完整性就必须使得读对象的(4)(5)步骤与
 // 时间段[t1,t2]重合。以下用反证法证明。
 // 假设该算法不能保证读到数据的完整性,并且有一个读步骤(以下讨论的都是针对改写步骤)的(4)、(5)步
 // 骤与时间段[t1',t2']有重合,即它会在写对象写d{USE}时去读d{USE}。那么读对象的第(1)、(3)步必须完成在
 // 时间t2之前,那么第(2)步完成在t2之前,那么第二个写步骤的第(1)步必然不能通过,因为它读到的using2此
 // 时必然不为-1,然而写步骤的这两个步骤的存在是前提,产生矛盾,所以假设不存在。即证。

2. C++模板的实现

// created by zleil
//
// 2005.9.19
//
// change:
// 2005.10.7 by zleil
//        把原来的CViewdata2内改成了模板实现,并更改了其get方法中第二步的一个bug
//        即:if( can_use ) ==> if( ! can_use )
//


#ifndef VIEWDATA_H
#define VIEWDATA_H

#include "viewdata_t.h"

// 两种使用方法
// 第一:get/put成对使用
//     1st CViewdata()或CViewdata( const T &init ),确保这个步骤完成后才能开启下一步
//     2nd 读写对象分别调用get,put方法
// 注意:参数类型T必须设置好其=运算符,详情请看test目录下的测试程序,
//       如果类型T的默认构造函数不能构造完整的数据,那么必须用初始化函数CViewdata( const T &init )
//
// 第二:{enter_get/leave_get} / {enter_put/leave_put}成对使用,T类型一般为指针类型
//       d0和d1分别指向两块存储区
//     1st CViewdata( const T &d_0, const T &d_1 )或者
//         CViewdata(),再连续两次put调用填入d_0和d_1。
//         保这个步骤完成后才能开启下一步
//     2nd 读对象操作:enter_get返回true时,表示可以对d指向的数据进行读,并且读操作从此开始,然后用户
//                     利用该返回指针进行读操作,最后调用leave_get方法结束读操作
//         写对象操作:enter_put返回true时,表示可以对d指向的数据进行写,并且写操作从此开始,然后用户
//                     利用该返回指针进行写操作,最后调用leave_put方法结束写操作

template
class CViewdata{
private:
 T d0;     // 只能由写对象更新
 T d1;     // 只能由写对象更新

 volatile int can_use;     // 只能由写对象写
 volatile int read_using;  // 只能由读对象写
 volatile int read_using2; // 只能有读对象写
public:
 CViewdata( const T &d_0, const T &d_1 );
 CViewdata( const T &init ); // 初始化后,必须是完整的数据
 CViewdata();

 bool get( T &d ); // 返回true,取到了数据,只能由读对象调用
 bool put( const T &d ); // 返回true,放入了数据,只能由写对象调用

 bool enter_get( T &d ); // 返回true,可以取数据,但取数过程持续到leave_get调用结束
 void leave_get();

 bool enter_put( T &d ); // 返回true,可以放数据,但放置数据的过程持续到leave_put调用结束
 void leave_put();

 // 帮助调试
 int get_can_use(){ return can_use; }
 int get_read_using() { return read_using; }
 int get_read_using2() { return read_using2; }

};

template< typename T >
CViewdata::CViewdata( const T &d_0, const T &d_1 )
{
// for enter/leave 用法
 d0 = d_0;
 d1 = d_1;

 can_use = 0;
 read_using = read_using2 = -1;
}

template< typename T >
CViewdata::CViewdata( const T &init )
{
 d0 = init;
 d1 = init;

 can_use = 0;
 read_using = read_using2 = -1;
}

template< typename T>
CViewdata::CViewdata()
{
 // default
#if 0
 d0.create();
 d0.vRIGHT=3.0f;
 d0.vLEFT=-3.0f;
 d0.vTOP=3.0f;
 d0.vBOTTOM=-3.0f;
 d0.vNEAR=0.1f;
 d0.vFAR=100.0f;
 d0.rotate_Y=0.0f;
 d0.rotate_X=-45*(3.14/180);
 d0.rotate_Z=0.0f;
 d0.tranX=0;
 d0.tranY=0;
 d0.tranZ=-10.0;

 d1 = d0;
#endif

 can_use = 0;
 read_using = read_using2 = -1;
}

template< typename T >
bool CViewdata::enter_get( T &d )
{
 bool ret = false;
 int R;

 do{
  // 1st
  R = can_use;
  // 2nd
  read_using2 = R;
  // 3rd
  R = can_use;
  // 4th
  if( read_using2 != R ){
   break;
  }
  read_using = read_using2;

  // 5th copy
  d = (read_using) ? d1 : d0;

  // ... 返回true,由调用者完成后续步骤
  ret = true;
 }while(0);

 return ret;
}
template< typename T >
void CViewdata::leave_get()
{
  // 6th
  read_using2 = -1;
  // 7th
  read_using = -1;
}

template< typename T >
bool CViewdata::get( T &d )
{
 bool ret = false;
 int R;

 do{
  // 1st
  R = can_use;
  // 2nd
  read_using2 = R;
  // 3rd
  R = can_use;
  // 4th
  if( read_using2 != R )
   break;
  read_using = read_using2;
  // 5th copy
  d = (read_using) ? d1 : d0;
#if 0
  if( ! d.iscomplete() )
   printf( "can_use=%d,using2=%d,using=%d ", can_use, read_using2, read_using );
#endif
  // 6th
  read_using2 = -1;
  // 7th
  read_using = -1;

  ret = true;
 }while(0);

 return ret;
}

template< typename T >
bool CViewdata::enter_put( T &d )
{
 int R1,R2;
 bool ret = false;

 do{
  // 1st
  R1 = read_using2;
  R2 = read_using;

  // 2nd
  if( R1 != -1 || R2 != -1 )
   break;
  if( ! can_use )
   d = d1;
  else
   d = d0;
  // can_use = can_use ? 0 : 1; 这一步由leave_put完成

  ret = true;
 }while(0);

 return ret;
}

template< typename T >
void CViewdata::leave_put()
{

 can_use = can_use ? 0 : 1;
}

template< typename T>
bool CViewdata::put( const T &d )
{
 int R1,R2;
 bool ret = false;

 do{
  // 1st
  R1 = read_using2;
  R2 = read_using;

  // 2nd
  if( R1 != -1 || R2 != -1 )
   break;
  if( ! can_use )
   d1 = d;
  else
   d0 = d;
  can_use = can_use ? 0 : 1;

  ret = true;
 }while(0);

 return ret;
}

typedef CViewdata CViewdata2;

#endif

阅读(1058) | 评论(0) | 转发(0) |
0

上一篇:几种特殊指针的定义

下一篇:短歌行

给主人留下些什么吧!~~