2005年10月01日

                                          (VC6实现的C++对象模型)

      

       C++对象模型中,类成员的分布情况:

1)        non-static 数据成员位于每个对象之中

2)        static 数据成员与所有的函数都位于对象之外

处理虚函数:

       类的所有虚函数地址放在一块,称之为虚拟函数表。

编译器为每个类对象插入一指针vptr 指向该虚函数表。

虚函数在函数表中的位置按一定的顺序,每个函数名对应着一个slot_id,调用的时候直接调用v-tableslot_id对应的函数。

上面是最基本的模型。至于virtual-base 多重继承 等的模型也是基于上述模型进行一些改变,具体等到后面再说~,先把简单的搞定了J

不考虑虚函数的数据布局

struct A

{

    void v_fun1(){};

    void v_fun2(){};

    int a;

    int b;

    int c;

    static int d;

};

main ()

{

    A a,b;

    cout<<FMT<<"sizeof(a) = "<<sizeof(a)<<endl;

    cout<<FMT<<"&a = "<<&a<<endl;

    cout<<FMT<<"&a.a = "<<&a.a<<endl;

    cout<<FMT<<"&a.b = "<<&a.b<<endl;

    cout<<FMT<<"&a.c = "<<&a.c<<endl;

    cout<<FMT<<"&b = "<<&b<<endl;

    cout<<FMT<<"&b.a = "<<&b.a<<endl;

    cout<<FMT<<"&b.b = "<<&b.b<<endl;

    cout<<FMT<<"&b.c = "<<&b.c<<endl;

    cout<<"\t——————————"<<endl;

    cout<<FMT<<"&a.d = "<<&a.d<<endl;

    cout<<FMT<<"&b.d = "<<&b.d<<endl;

    printf("\t&a.fun1 = %p\n",a.fun1);

    printf("\t&a.fun2 = %p\n",a.fun2);

    printf("\t&b.fun1 = %p\n",b.fun1);

    printf("\t&b.fun2 = %p\n",b.fun2);

    cout<<endl;}

// [输出结果]

      sizeof(a) = 12

       &a = 0012FF74

     &a.a = 0012FF74

     &a.b = 0012FF78

     &a.c = 0012FF7C

       &b = 0012FF68

     &b.a = 0012FF68

     &b.b = 0012FF6C

     &b.c = 0012FF70

     —————————

     &a.d = 0047873C

     &b.d = 0047873C

     &a.fun1 = 00401235

     &a.fun2 = 00401181

     &b.fun1 = 00401235

     &b.fun2 = 00401181

 

 

       对象的大小正好是3non-static变量的大小之和, a,b的地址之后,紧接着的是各自的数据成员的地址:non-static 成员变量分别存在于对象中 ab获得的static数据成员和函数地址(事实上并不是实际的函数地址)都是一样的,因此他们的位置与具体对象无关:成员函数以及static型数据成员则放置于对象之外,对象a,b共享成员函数以及静态成员变量J。可以用下面的图显示:


1

      

       由于数据成员的内存分布连续,那么我们很理所当然的写出下面的代码……

class A

{

    A()

    {

          memset(this,0,sizeof(A));

          // [取代下面的初始化]

        // a = 0;

        // b = 0;

        // c= 0;

};

};

 

 

       现在这样写是正确的,不过如果加入虚函数,问题就来了……

具有虚函数对象数据分布

struct A

{

    // [加上virtual]

    virtual void v_fun1(){cout<<"v_fun1"<<endl;};

    virtual void v_fun2(){cout<<"v_fun2"<<endl;};

    int a;

    int b;

    int c;

};

main()

{

    A a,b;

    cout<<FMT<<"sizeof(a) = "<<sizeof(a)<<endl;

    cout<<FMT<<"&a = "<<&a<<endl;

    cout<<FMT<<"&a.a = "<<&a.a<<endl;

    cout<<FMT<<"&a.b = "<<&a.b<<endl;

    cout<<FMT<<"&a.c = "<<&a.c<<endl;

    cout<<endl;

}

// [输出结果]

sizeof(a) = 16

       &a = 0012FF70

     &a.a = 0012FF74

     &a.b = 0012FF78

     &a.c = 0012FF7C

      

       A的函数加上virtual 之后,再看看结果的不同:

1)        对象a 的大小增大了4字节

2)        对象a的地址和a的第一个成员地址之间有4个字节的不明数据

 不明数据其实就是我们一开始提到过的vptr,编译器私下为对象加入的指针,指向v-table。而v-table中储存着类A的所有虚函数地址 v_fun1, v_fun2

 

 

       具体布局如下图所示:

2

 

 

为什么要加入这样的机制呢,或者说为什么选择了这样的模型呢?

              主要考虑的还是效率的问题……,For further info,please consult The c++ object model ~J

 

 

       再通过代码来验证一下~J~

      

验证思路:

如果存在vptr ,那么他保存的数据就是vitrual-table 的地址,那么,所有的虚函数的真实地址应该都在这里了,知道了地址后,

如何调用呢?

1.         使用正常的成员函数调用方式 (obj.*fun)( parm )

2.         非成员函数的方式调用,既然知道的是真实地址,那么如果再知道函数的原形就解决了,记得this 指针吧,(曾几何时,因为它还莫名其妙了一阵子,哈~),在VC6中,他是做为函数的第一个参数传入的,其他参数以成员函数的一样。

 

 

^_^ 就按照这样的思路,把找到的地址直接调用,哈~,希望别找出乱七八糟的地址(大不了程序崩溃)@$%%&(

      

// [获得vptr指针本身的地址就是&a]

int *addr_vptr  = (int*)&a;

// [获得vptr指向的地址(v-table 的首地址)]

int *vptr = (int*)((*addr_vptr));

// [获得第一个函数的地址]

int *p_v_fun = (int*)((*vptr));

 

 

// [现在函数地址已经找到了,调用一下看会不会程序崩溃 J~~~!!]

// [记得this指针吧,成员函数经过编译器的捣乱后的函数]

// [——把this做为参数传到成员函数中……]

typedef void(*PF)(A*);

PF pf = (PF)( p_v_fun );

 (*pf)(&a);

 

 

// [输出结果]

v_fun1

 

       哇,还真有那么回事,不会是碰巧吧——再来调用一下第二个虚拟函数。

 

 

// [virtual table中第2个存储单元地址]

vptr    = (int*)((*addr_vptr)+4);  

// [获得第二个函数的地址] 

p_v_fun = (int*)((*vptr));             

pf      = (PF)( p_v_fun );

(*pf)(&a);

 

 

// [输出结果]

v_fun2

 

 

       恩,的确是那么回事,HOHO。不信自己试试 ~J~

       继续……

memset 的使用导致错误。

       继续之前,再回顾一下前面分析的最后一段代码。

class A

{

    A()

    {

        memset(this,0,sizeof(A));

        // [取代下面的初始化]

        // a = 0;

        // b = 0;

        // c= 0;

};

};

       现在再看它,应该就能发现问题了!

记得constructor semantic 中说过,编译器初始化vptr的操作会放在constructor user-code 前,~ 类似下面的代码

// [Initialize vptr ……]

……

// [user code]

memset(this,0,sizeof(A));

终于看到狐狸尾巴了J。编译器初始化完vptr后,我们的user-code 又把vptr 给刷了~

编译器白忙了一场……L

 

 

继续刚才的话题

 

 

       上面虽然有了虚函数,但还没考虑到继承,现在分析一下drive-class中的数据分布,他是如何同base-class 联系起来的……

      

差点忘记今天是国庆节,嘿,休息休息~ 上网看看MM J

      

 

 

 

 

 

 

2005年09月30日

 

 

虽然简单,再复习复习,哈~

成员初始化列表与其他初始化的不同

 

 

// 1)

class A

{

A():m_str(0){}

string m_str;

}

// 2)

class A

{

A()

{

     m_str = 0;

}

 string m_str;

}

 

 

1) 2) 两种初始化方式有什么不一样呢?

看一下编译器层次上,他们可能的伪代码就清楚了:

 

 

// 1)

A()

{

     m_str.string::string(0);

}

// 2)

A()

{

     string _tmp;

     tmp.string::string(0);

     m_str.string::string(_tmp);

     tmp.string::~string();

}

 

 

 

 

       显然,第一种情况要比第二好多了,因为第二个加上了一个临时对象的初始化和销毁开销,而如果这两个开销比较大的时候,就会影响程序性能了J

成员初始化列表的执行顺序

光注意到好处还不行,问题还是存在的。看下面的代码:

 

 

class B{……}

class A

{

    A(const B& b):m_b(b),m_b_copy(m_b){……}

    B m_b;

    B m_b_img;

};

 

 

好象很理所当然的写法……,但是实际上存在错误?

       因为成员初始化列表的执行顺序是右优先级的,如同运算符,参数传递一样,知道是右优先级了那上面的代码当然是错的了……J (不知道是否所有的编译器都是右优先级,既然不知道,恩,就要避免这么写贝@#%^%)。

成员初始化列表与其他初始化的执行顺序

看到上面的错误,可能又会想到另外一个‘错误’。

 

 

class A

{

     A(int in):data(in)

     {

         data_img = data;

}

     int data;

     int data_img

}

 

 

 

 

 

 

上面的代码会有问题吗——究竟是 data(int) 先执行还是 data_img=data先执行呢?

答案是前者。

成员列表的初始化代码会被编译器插入到构造函数的user-code 之前。(好象所有编译器私底下增加的都在user-code 之前吧)J

何时必须使用成员初始化

1           const 数据成员

2           成员对象的带参数初始化

3           base-class 带参数初始化

4           引用变量的初始化

 

 

 

 

终于要开始认真的学软件工程了,突然想起,不久前和WK讨论spider时把两个模块因为处理的信息一致(信息内聚)说成了信息耦合,哈~  郁闷!#¥·%¥。

在没学之前,可是很向往着软件工程的,结果因为前段时间是打算考研而没下工夫,甚至逃课(不过是懒惰的借口罢了~)。现在时间又回来了,(今天早上的软工又逃了……),是该改改了,不改就找不到工作了 5555

说起看软件工程,才发现老师已经飙了好远,自己却从头看,真是郁闷。不过呢,我还是有信心在速度上和在质量上赶上进度的,嘿嘿~

 

 

什么是软件工程呢?

话说遥远的软件开发时代,不夸张的说,软件和某个人的模糊的想法一致,软件开发就是根据这一个不太清晰的想法指导下完成的。没办法,那年头通用硬件虽然很多了,软件却还madamadadane (from prince of tennis)……,软件开发还只是为数不多的计算机精英级人员的专利,并且软件也都是针对具体领域的,因此便导致了不太规范,个人意识占主导的软件开发方式。

随着硬件的不断升级,人们对软件的应用也不断提高,软件的数量及规模以很快的速度增加,软件的完成已经不再是开发的终结,随着用户的要求的提高以及使用过程中的需求改变,软件的维护(除BUG)和升级(对应硬件的升级,对应需求的升级……)逐渐占据了相当大的生产力,甚至很多软件是不可维护的。毕竟软件只不过是某个人,一些人,在某个时期的一种隐晦的想法,(有多少人对自己精力的鸡毛事情记得清清楚楚呢,那不是占用‘硬盘空间’吗……)时过境迁,天知道原来的软件是怎么写出来的……

于是,在19×8年的某一天,软件工程这个概念诞生了……

那可是软件开发领域的一个里程碑啊……

(先回答下问题J软件工程就是以工程学的方法指导软件的开发和维护——好象是这么说的,哈~ 分为 问题定义,问题分析,设计和编码测试,维护几个阶段

人们开始以工程管理的方法来指导软件开发,于是有了结构化程序设计方法。

……

困了 J

Copy semantic

bit wise semantic(以下简写为bws ^_^):

copy constructor(以下简称cc)

bit wise copy(bwc)

 

 

当某个class 展现bws 的时候,编译器就会对拷贝进行bitwise-copy

Bitwise-copy

       所谓的逐位拷贝就是类似下面的一种拷贝;

             

Class B    ……

Class A

{

       int   i;

       char *p;

       B   b

};

A a ,b;

a = b;

 

 

此时bitwise-copy 的效果类似于 a.i = b.i; a.p = b.p; 然后对b 施与类似的拷贝操作。递归的进行下去……

(也就是我们常说的浅拷贝——注意p,此时,b中的 p a 中的p 指向了同一个内存地址,如果程序过程中a 先于b 销毁,那么p所指的地址块已经被销毁,此时,如果b中的操作使用了p,或者是b销毁过程中再次销毁p都将导致程序错误,甚至崩溃~

什么时候展现了bws

 

 

       换句话来说可能好理解:编译器没必要为他合成copy constructor的时候(或者说合成品是无用的) 。什么时候不必要呢?先说一下什么时候必要吧 J

1)      A以组合形式包含类B的对象,而类B存在cc 的时候。此时,因为B对象需要调用cc ,而bitwise copy 的操作却是:递归的进行memberwise bitwise copy。所以编译器就必须给A合成一个cc 用以调用B对象cc,此时合成的A::cc中还要加入对A中数据成员的拷贝操作语句。

2)      在继承的情况下,假设在类A的继承链中,如果某个父类B存在cc那么,编译器就得为A合成cc以调用Bcc

3)      在存在virtual fun 的情况下,有可能需要。为什么是有可能呢?因为象这样的情况就不需要:类A virtual fun

A a, b;

a = b;

      

这样 bwc 已能胜任。

       而什么情况下需要呢?

       例如:

B:public A (B 实现了 A中的virtual fun)

A  *pt = new B;

       这种情况就得合成cc了,因为bwc会导致非预期的结果。

如我们所知的,A 中存在virtual fun 时,编译器会加入数据成员vpbl 指向vtbl,语句 pt = new B,根据标准是可以支持多态性质的,就是说*ptvpbl指向的是Bvtbl,此时如果用bwc 的话,那*pt获得的将是B中的A-subobj,那*ptvpbl 指向的将是Avtbl,显然这不是我们需要的结果,因此就必须合成cc,然后在cc中加入调整vpbl 的语句,使得vpbl 正确。

4)      Vitual base subobj …… 待看 ^_^ J

上述情况说明了什么情况下不展现 bws ,——,什么时候展现bws 应该也清楚了J

Return Value Of Custom-obj

 

 

再看一下自定义类型的值形式返回。

class A;

A fun()

{

       A tmp;

      

       return tmp;

}

如何实现对象 tmp 的返回呢?

c++object model 中说到,c++-father曾经采用的是这样的方法:

编译器给fun 增加一个引用参数,类似fun(A& _result)

然后在 fun 内部做些须更改

void fun(A& _result)

{

       A tmp;

       ……

       _result.A::A(tmp); // [copy constructor]

       return;

}

看到这里,我想大家都会发现其中的多余处理,何必还要tmp呢,直接处理_result 不就完 ……,正解!!这就是返回值形式在编译器层次上的优化方法(so-called NRV)

       上述函数可以被优化为

void fun(A& _result)

{

       // [因为在编译器层次,我们所说的定义通常不包含调用构造函数]

       _result.A::A();

       ……

       return;

}

 

 

但是J,如果fun 很复杂呢,假设一下下面的情况:

 

 

void fun()

{

       A tmp;

       scope1

       {

              do sth change to tmp…

              scope2

              {

                     A tmp2 = tmp;

                            do sth change to tmp2

                                   return tmp2;

              }

              … return tmp3;

             

       }

       return tmp;

}

 

 

此时如何优化?god knows~. 因此对于复杂的函数,NVR优化就会被屏蔽,何为复杂呢:当然是编译器自己定义并搞定了!!(这也是让我们烦的之一,天知道它什么时候进行了优化) J  maby~

 

 

Beats me:

The c++ model 中说的要使用NVR优化,那么该类必须explicit 定义copy constructor ,但是没有写原因(可能是显而易见吧…),我不知道为什么,唉,郁闷,脑袋不好使!谁知道告诉我一下,可能我最近比较晕……·#%%……*#……# ·好多星星啊·J  3x  advance

 

 

什么时候需要/不需要 copy constructor

设想这样一个类:

class A

{

       int i;

       int j;

       int k;

}; // [或者是类似的,浅拷贝就可以胜任的类]

针对Acopy操作,编译器会施以 bitwise-copy,可以得到优化而又不会导致任何副作用。那还要copy-constructor 干嘛呢? 的确,很多情况下,对于较懒的(象我)人来说无疑是件好事,哈~(又敲了几下键盘)。不过象上面说到的NRV优化的条件是class 必须提供explicit copy-constructor。而上面我们却希望采用bitwise-copy。显然XXXX不可兼得。不过什么时候会希望使用NRV优化呢?——在程序中存在大量的该类对象的memberwise copy,而这些又能够通过使用NRV 优化来提高速度时。

如果某个类可能有上面的情况发生,那么不用考虑,要的就是explicited-ccJ

 

 

参考:The C++ object model (v-chinese)

 

 

 

 

       很多C++教程都有说到,如果一个class 没有构造函数,那么编译器会暗地里给这个class 加上一个 default 构造函数。于是刚开始学习的时候,有的人便以为,在default 构造函数中会进行一些必要的初始化工作,再由于懒的缘故(me J ,于是便忽略了构造函数。

       其实不然,甚至恰恰相反,default constructor 的作用并不是为程序语言使用者服务的,而是由于编译器的需要。

 

什么是编译器的需要呢?  

一:父类有default constructor 而子类没有时

我们知道,如果一个类A继承于类B,如果他们都有构造函数,则在构造类A的对象a_obj 时,首先会调用类B的构造函数,然后再调用A的构造函数。那么应该如何实现呢?这就是编译器为我们做的事情了,为了实现这样的C++标准,编译器在A的每个构造函数中都加入了一些代码,负责调用父类的构造函数,并且这些隐藏的代码是添加在 usre_code 前面,这就是为什么B的构造函数的处理会先于A的原因。可能刚开始学习的时候,我们会以为是先调用了B的构造函数,然后再调用A的构造函数,其实不然,如果你清楚了刚才所说的‘隐藏的代码’,那么就知道,是先调用了A的构造函数,然后在A的构造函数中的user-code 之前调用了B的构造函数。这样我们就应该理解为父类的 user-code 总是先于 子类的  user-code 执行。

 

Class B

{

       B(){}

       ……

}

class A : public B

{

       A()

{

       // [likely hint code by compiler]

       B::B();

       // [user-code]

       ……

}

}

 

上面是A有构造函数的情况,那如果A的设计者没有加入构造函数呢。假设现在我们要构造A的对象,A a_obj; 既然C++标准都说了此时必须先执行B构造函数,那就得想办法去解决这个问题啊!——这就是default constructor 发挥的时候了。为了能实现上面的标准,编译器便为类A添加了default constructor,并在其中加入之前我们说的‘隐藏代码’。OK,这样就不违背C++标准了吧,HOHO……

这就是so-called 编译器的需要。

当然了这只是之一……

二:使用组合的时候

即在某类A(没有构造函数)中的数据成员,包含了类B的对象:这个时候如果构造了A对象,那么B对象也随之产生,但是由于A没有构造函数,而B对象的构造函数又必须在构造的时候调用,于是,default constructor 又有发挥余地了,在编译器为A添加该构造函数,并在构造函数中对B对象的构造函数进行调用。

 

Class B

{

       B(){}

              ……

}

class A

{

       B b;

       ……

       }

 

 

三:在包含virtual 函数的class

       C++中对virtual function 的支持采用的方法是:首先把所有的virtual function 都放置在一个表中,称之为 virtual function table(vtbl) ,然后在class中加入隐藏的指针 vptr 指向该table,……(知道前面的就OK),假设这样的一个类A,如果我们构造了A的某个对象,那么该对象的vptr 指针必须赋予一个地址,指向 vtbl。这个赋值步骤通常有编译器完成,它暗自在A的构造函数中加入了这个赋值语句。 我想现在大家应该知道 default constructor 的作用了,如果A没有了构造函数,那么编译器就为其增加一个,并加上vptr 的初始化语句。

四:带有 virtual Base Class class

       以前就没怎么用virtual base class ,概念不太清楚,过几天再补上,……哈!

总结:

       可见,所谓的编译器自动构造的default constructor 并不是为程序员服务的,它只做它关心的,哪有时间管我们的初始化杂事%#……,如果你的程序需要初始化,而你总是忘记,那试着相信上帝,god bless you~ ~

 

 

参考: C++对象模型 铁胆神候 J

 

 

2005年09月26日


   我在屋顶看书,你陪着我……
   我在湖边玩耍,你陪着我……
   我在夜里观星,你陪着我……
   ……
   你总是不耐烦的诉说着许多人文地理,我总是陶醉的倾听,
   我总是兴奋的告诉你许多技术以及我的梦想,欣赏着你陶醉的眼神~
   ……
  
   #·¥%·(*—*·#¥%
   原来是学校的horn
   原来已是梦醒时分~
  
   懒散的躺着,不愿让那依稀的向往消散……
   心情有些惆怅
   原来梦真的很美
  

   总是失眠,以前就经常想事情,但是那是想我所想,现在更经常想了,却是想我所烦。好不容易数到了1千多,好不容易睡着了,好不容易的进入我向往的时空…… ,唉,真希望~~

   收到朋友的往事不堪回首,马上回之: 睡觉时别想那么多,你想失眠啊,数绵羊去……
   于人,总是表现的如此无优无虑
   于己,却是惆怅万千~

2005年09月24日

天气转凉了~,瘦弱的我,总是不出意料的感冒了……

       我真的是很想考研,想着继续类无忧无虑的生活,夸张了点,哈~,不过肯定比工作爽,其实更重要的是,可以专心学我想学的。 每次静下心来考虑以后,或者是总结自我,总是发现这么多年,自己原来什么都没做好,不知不觉就21岁了,不知不觉大学就快完了,好多的计划没实现,甚至没有开始。唉,其实高中结束时有过这样的想法:以后上大学一定好好充实自己,努力的把所有的东西都给学了 ,哈~ 现在,突然之间,又回到了以前,想着研究生的生活,一定比大学轻松多了,有很多的时间去学喜欢学的的,有更多的时间去做喜欢做的,甚至还想到了可以好好的去欣赏(锁定)那些本科美女。哈!

       最后,我还是放弃了,为什么,我也不知道(知道也不能说 ~)。那天晚上和师兄通了次电话,死WK又说了些想考研的话,说考研多么的爽啊爽,真是……,害我晚上郁闷了好久~,其实无所谓了,放弃就放弃吧,唯一担心的就是怕找不到工作,这几天把以前的程序,整理一下目录,写个简历,在网上乱发,结果没一个回音,唉,老师还整天说计算机领域总体供不应求,害我信心十足的投入了找工作的行当, 我哭……·#·!##¥(#

       不考研了,总是觉得失落了些~,也不知道为啥,唉,郁闷!

 

       XXX 不知道想什么·~·#¥……·¥

 

>buffer_manager

idle_id_cont    // 空闲的buffer id

writed_id_cont  // 空闲的buffer id,不过已经被write

parsed_id_cont  // 空闲的buffer id,不过已经被parse

buffer_cont     // 缓冲区集合

mutex           // 互斥对象

 

>buffer

data            // 数据

flag            // 初始化为0 当被write or parse 时把他设置为 !flag

                // 也就是说,如果某个write/parse 线程返回该缓冲,

                // flag==0 时,说明该缓冲数据可以删除。

 

 

 

 

// [函数都用mutex互斥]

// [请求缓冲区]

buffer*buffer_manager::request_buffer(int type,int& buffer_id/*[out]*/)  ;

// [归还缓冲区]

void    buffer_manager::return_buffer(buffer* ret_buffer,int type);

// [清空缓冲区]

void    clear_buffer();

 

 

Buffer* buffer_manager::request_buffer(int type,int& buffer_id/*[out]*/) 

{

    // [当然可以针对每个 case 写一处理函数!]

    switch(type)

    {

    case type_spider:

        for(id in idle_id_cont)

        {

            // [由于下面限制了writed_id_cont.size() + parsed_id_cont.size()

            //个数小于或者等于 n ,因此spider 肯定会分配到空闲buffer]

            if(!(id in writed_id_cont) && !(id in parsed_id_cont))

            {

                idle_id_cont.remove(id);

                buffer_id = id;

                return buffer_cont[id];

            }

        }

        break;

       

       

        // [provided n is sum of (count of writer and parser threads) ]

    case type_writer:

       

        if(n <= writed_id_cont.size() + parsed_id_cont.size())

            // [不分配给该writer 线程,避免出现spider等待]

            return NULL;       

       

        // [优先分配已经被parse的缓冲]

        if(!parsed_id_cont.empty())

        {

            buf_id = parsed_id_cont.back();

            parsed_id_cont.pop_back();

            idle_id_cont.remove(buf_id);

            buffer_id = buf_id;

            return buffer_cont[buf_id];

        }

        else

        {

            buf_id = idle_id_cont.back();

            idle_id_cont.pop_back();

            buffer_id = buf_id;

            return buffer_cont[buf_id];

        }

        break;

       

    case type_parser:

       

        if(n <= writed_id_cont.size() + parsed_id_cont.size())

            // [不分配给该writer 线程,避免出现spider等待]

            return NULL;       

       

        // [优先分配已经被write的缓冲]

        if(!writed_id_cont.empty())

        {

            buf_id = writed_id_cont.back();

            writed_id_cont.pop_back();

            idle_id_cont.remove(buf_id);

            buffer_id = buf_id;

            return buffer_cont[buf_id];

        }

        else

        {

            buf_id = idle_id_cont.back();

            idle_id_cont.pop_back();

            buffer_id = buf_id;

            return buffer_cont[buf_id];

        }

        break;

    }

}

 

// [归还缓冲区]

void    buffer_manager::return_buffer(int buffer_id,int type)

{

    // [可以针对每个 case 写一处理函数!]

    switch(type)

    {

    case type_writer:

        buffer * pbf = buffer_cont[buffer_id];

        pbf->flag = !pbf->flag;

        if(pbf->flag)

            writed_id_cont.push_back(buffer_id);

        else

            clear_buffer(buffer_id);

        break;

       

    case type_parser:

       

        buffer * pbf = buffer_cont[buffer_id];

        pbf->flag = !pbf->flag;

        if(pbf->flag)

            parsed_id_cont.push_back(buffer_id);

        else

            clear_buffer(buffer_id);

        break;

    }

    idle_id_cont.push_back(buffer_id);

}

 

 

 

 

 

最近只想把以前的小程序好好整理,改改简历,提高简历的魅力(至少也得减少一点排斥力,哈~), 结果感觉动力不太明显,活干得不怎么利落,面对以前乱七八糟的东西,一下也拾不起来,生活习惯一下改变,也有一点不太习惯……。唉……

       WK说他要做一个企业内部的“信息搜索工具”,暂且就这么叫吧,^_^ 想和我讨论一下以前我写的爬虫模型,以及相关实现。于是就重新拿起未完善的spider 代码,重新研究。其实本来我也打算把这个改一下,好提前交了毕业设计。

       以前写得较急,说到总的模型,也曾经考虑过,不过没太具体,写代码时,总是基于方便,临时改变了许多,最后还是成了个大杂烩,哈~ 是该改改了,和WK花了一天时间讨论,终于讨论了个比较清晰的模型,看起来好象也挺不错,可以写代码了,因为WK只要求企业内部网页搜索,实在是很轻量级的数据,因此什么URL数据库,什么URL消重,页面保存啊几乎都不会成为瓶颈,不过我却想把URL数据存储结构先设计一下,哈~ 因为根据我21年的经验,如果我现在不设计,以后其他的全部写完了,我的动力周期就差不多到低谷了,肯定懒得写了。 因此…… EH~··# ,我还是要先设计一下具体的数据结构,以及如何优化,当然了,基本的思路已经定下:以trie 树为基本数据结构,目的是消重实在很快,尽管缺点是占用N大的额外空间。 ……

…………………………

只顾着改后面的结构,忘记把URL数据处理给分离开了……, spider_manager 负责的只有对各个 downloader 的控制 start stop suspend resume shutdown … 。至于 URL 数据库和URL队列(功能同cash)应该组合为 UrlManager 这样就差不多了,

哈……

…………………………

关于分布式, 由于WK的企业内部检索要求不高,讨论时把这个给忘记了,我要的结果,当然是要加进去的,总的来说现在的模型改变应该不大。主要是加入一个新的网络模块deliver_manager,负责各个spider 之间的互动(当然是为了分压),如果能让每个spider 所下载的URL数量相差不大,那应该算较优吧, 至于如何deliver ,我打算先参考别人的论文,使用他们提供的经验函数,先进行尝试,以后有经验了,才进行修改。

至于deliver_manager 的工作,应该是介于html_parser  url_manager 之间。…… 还没想好 HOHO

…………………………

把IO_Writer 给放到Parser 之后了,觉得这样应该更好些(实现了再证实了!!),因为简化了PagebufferManager 的处理,同时让互斥分配的过程更加简单,减少因为互斥访问而让 Downloader  等待的时间。

2005年07月15日

http://msdn.microsoft.com/msdnmag/issues/03/10/NET/default.aspx