2007年8月20日星期一

zz 推荐我写的一个C++多线程程序开发库

http://www.chinaunix.net 作者:duanjigang 发表于:2007-06-21 19:36:17
发表评论】 【查看原文】 【C/C++讨论区】【关闭

代码从写出来到整理完成有好久了,如果有什么疑问或者代码中发现有缺陷,直接与我联系[email]duanjigang1983@126.com[/email]

概述一下原理和每个类的作用
(1): CTask 一个任务基类,作为线程的参数用
(2): CWorkThread:工作线程类,轮询检测参数是否可用,如果可用的话,
就去调用参数CTask的执 行函数Execute,如果参数不可用就等待
(3): CWorkQueue:是一个任务队列,里面装载CTask子类的对象,是一个对象的容器
(4): CWorkThreadPool:工作线程池,其中包含了工作线程组,调度线程,调度线程不停的检测
任务队列中是否有可用任务对象,如过取到一个有效的任务对象,就从工作线程组中查找出来
一个空闲的工作线程,把这个任务交给这个空闲的线程去执行,如果没有空闲线程则创建新
的工作线程并添加到工作线程组中。
(1),(2),(3),(4)组成了库的核心,可以稍加修改进行应用
其他附属类:
(5):CSimpleUDP 一个简易的封装的UDP通讯类
(6):CLogHelper 一个线程安全的日志类,将日志存储到指定文件并能打印到标准输出
特点:支持windows和Linux平台

使用举例:CUDPClient 和CUDPServer 一个线程发送UDP包,另外一个接收UDP包
使用方法
在src目录执行make命令,相应的库将生成到/usr/local/lib下
然后在/etc/ld.so.conf中添加/usr/local/lib
保存退出
执行ldconfig -v
开发时,只需要新建的类继承CTask类,然后将编译好的Tasklib库链接进去就可,Tasklib封装了main函数和线程以及任务队

列的管理,最好修改UDPClient的例子
日志:linux系统存放在/cucme.log,windows存放在C:\cucme.log中,如需修改,直接修改头文件,重新编译库。
注意:在windows先使用时最好将库文件一起重新编译,链接使用lib文件有异常存在。另外运行UDPClient例子时,首先
将UDPCLient.cpp中的IP地址进行修改,否则发送目的地错误。server将不能接收到。

代码有些地方可能会写的比较臭,希望高手赐教,更希望有朋友能在系统框架设计上给出好的建议。
如果有改进或者新的应用例子,我会及时贴上来的
饿的不行了,先回去吃饭了

[ 本帖最后由 duanjigang 于 2007-5-31 18:54 编辑 ]

cucme.tar.gz



sohusina 回复于:2007-05-31 19:38:27

我喜欢开源,多交流好。


wuxiangzhi 回复于:2007-05-31 20:16:29

down下来了,有空看看。支持共享


微雨骑驴入剑门 回复于:2007-06-01 09:16:55

支持


chinaljj 回复于:2007-06-01 10:45:00

支持


duanjigang 回复于:2007-06-01 11:03:02

为了对整个库的结构理解起来容易些
修改Src下的makefile

####makefile for lib file

####author:cucme

#!/bin/sh

SRC= Task.cpp TaskQueue.cpp WorkThread.cpp WorkThreadPool.cpp ThreadMain.cpp

default:

g++ -I ../include -D__LINUX__ -shared -fPIC $(SRC) -o /usr/local/lib/libTask.so

g++ -I ../include -D__LINUX__ -shared -fPIC LogHelper.cpp -o /usr/local/lib/libLogger.so

g++ -I ../include -D__LINUX__ -shared -fPIC SimpleUDP.cpp -o /usr/local/lib/libudp.so

framwork:

g++ -c -I ../include -D__LINUX__ Task.cpp TaskQueue.cpp WorkThread.cpp WorkThreadPool.cpp ThreadMain.cpp LogHelper.cpp

g++ -o mainFram *.o -lpthread

clean:

rm -fr /usr/local/lib/libTask.so /usr/local/lib/libudp.so /usr/local/libLogger.so


make framwork 将基本的库和主线程链接起来生成程序主框架,这个时候由于没有链接进来新的子类
所以整个系统空闲,运行
./mainFram

我们可以看到系统启动的一个线程空闲着,打印日志:

[Fri Jun 01 10:23:28 2007]: ##############################主线程启动成功!##################################

[Fri Jun 01 10:23:33 2007]: 线程001: 参数为空[CWorkThread::Run()]

[Fri Jun 01 10:23:38 2007]: 线程001: 参数为空[CWorkThread::Run()]

[Fri Jun 01 10:23:43 2007]: 线程001: 参数为空[CWorkThread::Run()]


如果链接进去一个CTask类的子类对象的话,这个线程就会去执行这个任务,如果链接的CTask类的子类对象个数大于一个,系统自动创建新的线程来处理,防止任务对列中堆积过多的任务,从而影响实时性。
在windows下也可直接用VC编译src下的文件运行就可以得到系统框架程序

[ 本帖最后由 duanjigang 于 2007-6-1 11:07 编辑 ]


枫叶无霜 回复于:2007-06-01 11:06:47

这些东西虽然偶看不懂,但是偶一直很佩服懂技术的银。。支持一把。。

  有时间教教偶技术吧。。啊门。。。:em15:


leeews 回复于:2007-06-01 11:09:16

俺是过来支持的


duanjigang 回复于:2007-06-01 11:09:37

引用:原帖由 枫叶无霜 于 2007-6-1 11:06 发表
这些东西虽然偶看不懂,但是偶一直很佩服懂技术的银。。支持一把。。

  有时间教教偶技术吧。。啊门。。。:em15:


霜霜给脸啊,不过我现在还不能教mm,我在学习中。。。。:mrgreen::mrgreen:


醉卧水云间 回复于:2007-06-01 12:47:13

看了几分钟, 简评一下:


static void ThreadSafePrint(const char * szMsg)

{

if(!Initted)

{

InitLock(&LockPrint);

Initted = true;

}

LockOn(&LockPrint);

#ifdef __DEBUG__

printf(szMsg);

#endif

LockOff(&LockPrint);

}



printf不需要lock, #ifdef 位置也不对



static int InitNetWork()

{

#ifdef _WIN32

WSAData w;

if(WSAStartup(0x0101, &w) != 0)

{

return -1;

}

#endif

srand((unsigned)time(NULL));

return 1;

}



想兼容win95? 却用ws2_32.lib?

static int LockOn(LockType* lock) 

这个函数找了我两分钟, 既然是c++, 为什么不做成类成员而用c风格?


static void SLEEP(int t1, int t2)

{

struct timeval tm;

tm.tv_sec = t1;

tm.tv_usec = t2;

select(0, NULL, NULL, NULL, &tm);

}



没必要select, 用usleep, 也不要两个参数, 用着麻烦.

class CWorkThread 这个类封装古怪, 无法做基类用. 建议看看成熟的实现. 已经有很多了.

个人以为, 不推荐用这个库.

[ 本帖最后由 醉卧水云间 于 2007-6-1 12:48 编辑 ]


duanjigang 回复于:2007-06-01 14:21:09

引用:原帖由 醉卧水云间 于 2007-6-1 12:47 发表
看了几分钟, 简评一下:
#ifdef __DEBUG_ ...


首先:
printf不用lock在多线程的程序里面打印确实是会出现乱码的,我是先发现打印的乱码问题才加入Lock的。
其次:
位置不对,是否应该写成这样

#ifdef __DEBUG__

LockOn(&LockPrint);

printf(szMsg);

LockOff(&LockPrint);

#endif


如果是的话,原来的写法也是行得通的,只不过在无有__DEBUG__宏的时候空Lock了一次
并不会造成错误,不过考虑细致点的话,赞成你的说法。
另外:
CWorkThread类的意图不知道楼上体会到没有,而是尽量让CWorkThread类的线程体跟具体的任务不要相关,把具体的任务跟CTask绑定了。
其实这个模型的最初样子是:
多个工作线程启动运行,任务对列中的任务是由主线程动态添加进去的,这样就能处理诸如消息转发,数据处理等发生效率比较高的任务,也就是任务对象 的个数不限制或者未知的情况,只是由于那样做的话,CWorkThread要牵扯到释放内存,这个我还没有处理好,所以先作成现在这个模型。
最后:
initnetwork那个实现确实是由于我才疏学浅对windows下的一些东西并不是很懂,才勉强作成那样的。还有Lock这个操作到目前为止,只牵扯到Lock和UnLock两个方法,所以偷懒没有作成类。个人认为没有必要这样做
关于实现上的问题应该是比较容易修改的,但是类的设计上确是很重要的,如果CWorkThread类封装古怪,希望楼上给点意见,指出哪里不太合适,如果有更好的设计,欢迎赐教!:P:P:P

[ 本帖最后由 duanjigang 于 2007-6-1 14:34 编辑 ]


duanjigang 回复于:2007-06-01 15:05:05

代码中带的UDP的例子实际上模拟的是两个进程
做了一个读写者的例子(直接将rw目录解压到cucme目录下进入rw目录make就可),放上来,Write向string队列中不定期写入数据,Read不定期从string队列中读取消息,写20次,读20次,使用了一个Lock实现资源互斥访问
写者的执行函数

int CWriteTask::Execute()

{

SLEEP(rand()%3, 500);

char szMsg[128];

string str;

sprintf(szMsg, "告诉你%d次了,不要乱扔东西\n", ++m_nWriterCounter);

LockOn(g_newLock);

g_strQueue.push(string(szMsg));

LockOff(g_newLock);

str = "写入:" + string(szMsg);

theLogger.LogMessage(str, true);

return 1;

}


读者的执行函数

int CReadTask::Execute()

{

SLEEP(rand()%5, 300);

string str;

if(!g_newLock)

{

return 1;

}

LockOn(g_newLock);

if(!g_strQueue.empty())

{

++m_nReaderCounter;

str = g_strQueue.front();

g_strQueue.pop();

}

LockOff(g_newLock);

if(str != "")

{

str = "读取:" + str;

theLogger.LogMessage(str, true);

}

return 1;

}


makefile

#!/bin/sh

default:

g++ -D__LINUX__ -c -I ../include *.cpp

g++ -o rw *.o -L /usr/local/lib -lTask -lpthread -ludp -lLogger

clean:

rm -fr *.o rw


通过WriteTask.(h/cpp)和ReadTask.(cpp/h)四个类文件,我们只要编译后的o文件连接进库的主框架就能执行了
说心里话,俺还是比较喜欢这种模型的,不必去管程序入口,一心做好线程要做的事情就是了 ^_^

[ 本帖最后由 duanjigang 于 2007-6-1 15:09 编辑 ]

rw.rar


醉卧水云间 回复于:2007-06-01 18:00:32

引用:
首先:
printf不用lock在多线程的程序里面打印确实是会出现乱码的,我是先发现打印的乱码问题才加入Lock的。



我看到ThreadSafePrint这个函数就已经皱眉头了. 写这个函数本身就说明你没有理解VC的库. 你写的是multi-thread库, 却和vc的single-thread库连接, printf能不出错?

引用:
其次:
位置不对,是否应该写成这样


CODE:[Copy to clipboard]#ifdef __DEBUG__
LockOn(&LockPrint);
printf(szMsg);
LockOff(&LockPrint);
#endif



为什么不全包括?



#ifdef __DEBUG__

if(!Initted)

{

InitLock(&LockPrint);

Initted = true;

}

LockOn(&LockPrint);



printf(szMsg);



LockOff(&LockPrint);

#endif



引用:
如果是的话,原来的写法也是行得通的,只不过在无有__DEBUG__宏的时候空Lock了一次
并不会造成错误,不过考虑细致点的话,赞成你的说法。



你是在写库, 不是写应用程序, 库里面一大堆无用代码影响太大.

引用:

另外:
CWorkThread类的意图不知道楼上体会到没有,而是尽量让CWorkThread类的线程体跟具体的任务不要相关,把具体的任务跟CTask绑定了。
其实这个模型的最初样子是:
多个工作线程启动运行,任务对列中的任务是由主线程动态添加进去的,这样就能处理诸如消息转发,数据处理等发生效率比较高的任务,也就是任务对象 的个数不限制或者未知的情况,只是由于那样做的话,CWorkThread要牵扯到释放内存,这个我还没有处理好,所以先作成现在这个模型。
最后:
initnetwork那个实现确实是由于我才疏学浅对windows下的一些东西并不是很懂,才勉强作成那样的。还有Lock这个操作到目前为止,只牵扯到Lock和UnLock两个方法,所以偷懒没有作成类。个人认为没有必要这样做
关于实现上的问题应该是比较容易修改的,但是类的设计上确是很重要的,如果CWorkThread类封装古怪,希望楼上给点意见,指出哪里不太合适,如果有更好的设计,欢迎赐教!



看了CWorkThread, mutex的实现, 其他部分我都没太细看, 但已经可以看出很多问题了. 如果你看过一些成熟的跨平台线程实现 再来对比你的实现应该可以发现差距, CWorkThread没有重用性, mutex没封装等等, 建议你多看看别人的代码然后再写库, 库比程序的要 求更高更严格. 象printf输出乱码的问题是基础, 你的基础有点不牢. 库对你来说还是偏难的任务, 对我也一样:lol:


duanjigang 回复于:2007-06-01 18:10:33

多谢“醉卧水云间 ”的指点,我继续改进吧,不管难与不难,都是该努力的方向啊
:P:P:P

[ 本帖最后由 duanjigang 于 2007-6-1 18:12 编辑 ]


枫叶无霜 回复于:2007-06-02 10:33:51

引用:原帖由 duanjigang 于 2007-6-1 11:09 发表

霜霜给脸啊,不过我现在还不能教mm,我在学习中。。。。:mrgreen::mrgreen:



  恩恩。。那学会了一定要教偶啊。。。。:mrgreen:


柳五随风 回复于:2007-06-02 12:24:05

引用:概述一下原理和每个类的作用
(1): CTask 一个任务基类,作为线程的参数用


值得商榷.

引用:(2): CWorkThread:工作线程类,轮询检测参数是否可用,如果可用的话,
就去调用参数CTask的执 行函数Execute,如果参数不可用就等待


轮训?自我调度?干嘛不是thread pool调度worker thread, worker thread再从thread pool里面 的queue manager的task pool(也可以防在thread pool)里面take一个task,再执行一个command呢?
引用:(3): CWorkQueue:是一个任务队列,里面装载CTask子类的对象,是一个对象的容器

thread queue和thread pool的关系需要很好的考虑,比较复杂.
引用:(4): CWorkThreadPool:工作线程池,其中包含了工作线程组,调度线程,调度线程不停的检测
任务队列中是否有可用任务对象,如过取到一个有效的任务对象,就从工作线程组中查找出来
一个空闲的工作线程,把这个任务交给这个空闲的线程去执行,如果没有空闲线程则创建新
的工作线程并添加到工作线程组中。


调度线程纯粹多余,thread pool本身就是自适应的,他自己本身就是scheduler.另外"从工作线程组中查找出来
一个空闲的工作线程"更是值得商榷?什么叫查找?这得多大开销呀.


duanjigang 回复于:2007-06-02 17:45:57

首先很感谢楼上提得意见。

下面对于您提的问题做点解释,希望能够对这个不太称职的“库”提出更多的改进:

第一:
CTask类的设计是为了将线程每次执行时要做的事情彻底与线程本身独立开来,而把这些东西都交给CTask类,要做什么是 CTask自己的事情,线程只会在一个while循环里为这个任务提供时间片而已,这样,我们的线程就比较有普适性,符合线程机制产生的最初目的,就是为 了解决多任务并发执行。
第二:
关于调度线程的取舍与否我也仔细斟酌过,但是最终还是选择了保留这个调度线程,因为做这个系统时,我希望系统的功能能够很强大,比 如,能实现对工作线程的实时管理与监视,有几个线程空闲,几个线程繁忙,每个线程工作了多长时间等,还有,想对任务队列中的任务对象加上优先级和有效时间 段,当任务过期时就需要扔掉或者优先级很高时就需要优先执行等机制,而且任务队列应该是一个缓冲去,比如有10个工作线程,而队列中的任务可能有20个等 等,这些对线程的管理和队列的管理与监视等都需要有一个线程来完成,所以添加了调度线程,只不过在目前这个版本中只做了任务的分配,所以您觉得它是多余 的。
不知道您对我的解释是否满意?

[ 本帖最后由 duanjigang 于 2007-6-2 17:47 编辑 ]


jiucailiuxing 回复于:2007-06-06 13:48:58

bool CSimpleUDP::InitRcvSock(const int nRcvPort)
{
int sockfd;
bool bRet = true;
struct sockaddr_in addr;

do {
//创建套接字描述符
if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
{
bRet = false;
break;
}

//绑定IP地址和端口
memset((void*)&addr, '\0', sizeof(struct sockaddr_in) );
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htons(INADDR_ANY);
addr.sin_port = htons(nRcvPort);
//进行绑定
if(bind(sockfd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)) < 0)
{
bRet = false;
break;
}
}while(false);

//赋值套接字描述符的值
m_nSock = sockfd;
return bRet;
}



如果创建socket失败和绑定失败就退出循环, 那这个循环有什么意义呢?

[ 本帖最后由 jiucailiuxing 于 2007-6-6 13:51 编辑 ]


duanjigang 回复于:2007-06-06 15:05:19

仔细分析了一下,上面的代码应该改成这样就更合适了

bool CSimpleUDP::InitRcvSock(const int nRcvPort)

{

int sockfd;

bool bRet = false;

struct sockaddr_in addr;

do

{



if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) break;

addr.sin_family = AF_INET;

addr.sin_addr.s_addr = htons(INADDR_ANY);

addr.sin_port = htons(nRcvPort);

if(bind(sockfd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)) < 0)

{

break;

}

bRet = true;

m_nSock = sockfd;

}while(false);

return bRet;

}


听以前的一个同学分析说这么做可以把冗长的语句序列模块化,比如类似于

socket....

bind...

listen...

accept


这样的大段程序,可以用一个

do

{



}while(false)


把它模块化,感觉好像有点道理,就这么写了
现在我发现这种做法的一个好处是,它可以替代goto语句直接跳转到一段程序的结尾
对于这个函数你可能觉得并没有必要,因为它的返回值为bool,直接返回false就行了
但是对于返回为void类型,并且出错时需要事务回滚或者出错处理的,这种做法就比较合适了,你可以在
while(false)
之后作出错处理,要是不出错就在
do
{
}
的结尾处返回
不知道这样的说法是否合理?


jaffaz 回复于:2007-06-06 18:03:26

支持一下,收藏。
需要的时候再看一下


foochow 回复于:2007-06-20 15:00:21

支持一个先..
直接用ACE不就可以了?


duanjigang 回复于:2007-06-20 16:29:24

引用:原帖由 foochow 于 2007-6-20 15:00 发表
支持一个先..
直接用ACE不就可以了?


知道有ACE的,为了自己提高下,所以找个目标
:P
多谢上面各位的建议,这几天好好学习一下,希望不久能推出改进版!
:em02:


cookis 回复于:2007-06-20 19:11:21

你可以参考一下网络上的"linux下通用线程池"


lgfang 回复于:2007-06-21 08:55:32


static void ThreadSafePrint(const char * szMsg)

{

if(!Initted)

{

InitLock(&LockPrint);

Initted = true;

}

LockOn(&LockPrint);

#ifdef __DEBUG__

printf(szMsg);

#endif

LockOff(&LockPrint);

}



没下载你的代码,从1楼的回复中看到上面的代码。LZ可以应用一个C++编程常用的机制:"resource acquisition is initialization"。

按照这种机制,你的LockOn和LockOff两个函数应该分别做成一个类的构造函数和析构函数。这样,加锁时只需构造一个相应的局部对象。而 解锁则不用你操心了,当这个对象超出作用域时会自动析构(并解锁)。这样就避免了你由于各种原因(疏忽、异常)没有调用解锁函数。

[ 本帖最后由 lgfang 于 2007-6-21 10:13 编辑 ]


duanjigang 回复于:2007-06-21 09:21:27

引用:原帖由 lgfang 于 2007-6-21 08:55 发表
按照这种机制,你的LockOn和LockOff两个函数应该分别做成一个类的构造函数和析构函数。这样,加锁时只需构造一个相应的局部对象。而 解锁则不用你操心了,当这个对象超出作用域时会自动析构(并解锁)。这样就避免了你由于各种原因(疏忽、异常)没有调用解锁函数。


谢谢。
我对你的说法的理解是:
由于不同的资源使用不同的锁,所以在构造这个特殊类的时候,我们应该重载一个将锁变量作为参数的构造函数,然后在声明这个对象时就构造一个局部对象来加锁,当局部对象退出时就自动解锁。
但是lgfang同学有没有想到会遇到这样的情况。比如函数:

void PlayGame()

{

LockOn(&LoginLock);

Login();

LockOff(&LoginLock);

while(haveMoney())

{

Play();

//这里可能会花费很多时间

}

}

如果按照LZ的意思,可能会将代码改造为如下的方式

class Lock

{

LockType* Lk;

public:

Lock(LockType*lk){Lk = lk;LockOn(Lk);}

~Lock(){LockOff(Lk);}

}

void PlayGame()

{

Lock lk(&LoginLock)

Login();

while(haveMoney())

{

Play();

//这里可能会花费很多时间

}

}

这样的话,在整个while(haveMoney())期间,lk都没有destruct掉,LoginLock是被LockOn的,其它对象的调用就会阻塞的,显然这不是我们想要的效果

[ 本帖最后由 duanjigang 于 2007-6-21 09:23 编辑 ]


cookis 回复于:2007-06-21 09:24:27


void PlayGame()

{

{

Lock lk(&LoginLock)

Login();

}

while(haveMoney())

{

Play();

//这里可能会花费很多时间

}

}



可以这样


duanjigang 回复于:2007-06-21 09:32:57

接着上面的。
其实也可以这么改造下:

void PlayGame()

{

{

Lock lk(&LoginLock)

Login();

}

while(haveMoney())

{

Play();

//这里可能会花费很多时间

}

}


这样就可以及时的LockOff了,也就是destruct掉lk,但是个人感觉对于程序的可读性有些影响,不如

void PlayGame()

{

LockOn(&LoginLock);

Login();

LockOff(&LoginLock);

while(haveMoney())

{

Play();

//这里可能会花费很多时间

}

}
看起来一目了然些。而且感觉要求用大括号把局部对象括起来跟LockOn和LockOff要配对是一样的意思
不过你的这种思想确实不错,很感激。
顺带提一下,对于为了控制作用域而显式调用destructor的方式,如下的代码:

void PlayGame()

{

Lock lk(&LoginLock)

Login();

lk.Lock::~Lock();

while(haveMoney())

{

Play();

//这里可能会花费很多时间

}

}

你我估计都不会去采用吧,因为这跟显式LockOn和LockOff没区别。

[ 本帖最后由 duanjigang 于 2007-6-21 09:46 编辑 ]


duanjigang 回复于:2007-06-21 09:34:16

呵呵,还没写出来,就有人回复了


lgfang 回复于:2007-06-21 09:50:00

引用:

void PlayGame()

{

{

Lock lk(&LoginLock)

Login();

}

while(haveMoney())

{

Play();

//这里可能会花费很多时间

}

}


这样就可以及时的LockOff了,也就是destruct掉lk,但是感觉对于程序的可读性有些影响,个人感觉不如显式调用LockOn和LockOff来的明白些。



1,在实际项目中,是安全性、健壮性重要还是可读性重要?
我已经不止一次目睹了因为忘记释放资源导致程序出问题。而其中很多应该都是可以避免的。

2,具体到你举的例子,对可读性有影响是因为你的代码本身的问题,和RAII机制无关。既然LoginLock是只为login准备的,为什么不把代码改成这样:

bool login(){

Lock lk(&LoginLock);

// login

}



void PlayGame()

{

if ( ! login()) { // ...

}



while(haveMoney())

{

Play();

//这里可能会花费很多时间

}

}






lgfang 回复于:2007-06-21 09:55:40

引用:
顺带提一下,对于为了控制作用域而显式调用destructor的方式,如下的代码:


void PlayGame()

{

Lock lk(&LoginLock)

Login();

lk.Lock::~Lock();

while(haveMoney())

{

Play();

//这里可能会花费很多时间

}

}


你我估计都不会去采用吧,因为这跟显式LockOn和LockOff没区别。



确实有需要的话,你可以写一个解锁的成员函数。


lgfang 回复于:2007-06-21 09:59:00

引用:
感觉要求用大括号把局部对象括起来跟LockOn和LockOff要配对是一样的意思


不一样。括号不匹配,编译器会报错。没调用LockOff,编译器是无从知道的


duanjigang 回复于:2007-06-21 10:06:50

引用:原帖由 lgfang 于 2007-6-21 09:50 发表


1,在实际项目中,是安全性、健壮性重要还是可读性重要?
我已经不止一次目睹了因为忘记释放资源导致程序出问题。而其中很多应该都是可以避免的。

2,具体到你举的例子,对可读性有影响是因为你的代码 ...


其实你也可以看到,牵扯到锁操作的地方,我都是把锁操作直接限制到最小的代码范围内,而且是直接对锁进行操作,这样减少了功能和实现的关联,关于不把锁操作封装成类的原因我在前面的回复中已经说明了一些原因。
上面的Login只是一个例子而已。
至于申请资源要释放的问题,我想这个是对程序员的要求。对我来说,代码的风格和易读性重要些,如果能把程序写成自然语言的方式,那最好不过了。健壮性很重要,但它是需要coder的细心来保证的。
:D


duanjigang 回复于:2007-06-21 10:21:22

引用:原帖由 lgfang 于 2007-6-21 09:50 发表
2,具体到你举的例子,对可读性有影响是因为你的代码本身的问题,和RAII机制无关。既然LoginLock是只为login准备的,为什么不把代码改成这样:


这个例子确实是这样的问题
但是在我的代码中,lgfang同学可以看到,风格是这样的

void CWorkThreadPool::AddThread(CWorkThread *th)

{

if(th != NULL)

{

LockOn(&g_ThreadPoolLock);

m_pThreadQueue[m_nThread++] = th;

LockOff(&g_ThreadPoolLock);

}

}

并不是

LockOn(&g_ThreadPoolLock);

AddThread(th);

LockOff(&g_ThreadPoolLock);

酱紫滴。:em02:


cookis 回复于:2007-06-21 10:38:57

强烈建议参考一下ACE 的同步机制


duanjigang 回复于:2007-06-21 12:18:19

引用:原帖由 cookis 于 2007-6-21 10:38 发表
强烈建议参考一下ACE 的同步机制


好得:em02:


hnynes 回复于:2007-06-21 19:05:51

先down了,看看再说.


ghostwwl 回复于:2007-06-21 19:36:17

支持一个 Down了 有时间看看




原文链接:http://bbs.chinaunix.net/viewthread.php?tid=943245
转载请注明作者名及原文出处

没有评论: