创建安全的线程

有人这样阐述线程与进程的区别:
²         线程:资源竞争的最小单位
²         进程:资源分配的最小单位
这里从资源管理的角度很好的区分开了线程与进程。这里我们不去深究各个具体平台是怎么实现线程或进程的,这里我们只需要明白一点:一个进程的崩溃一般不会影响到与他跑在同一系统上的其他进程,一个线程的以外死亡却会连累与他同处一进程的其他线程。
一般系统提供注册线程的C格式的API大致如下(这里暂时不提java等语言内建任务):
//FUNCTION:注册一个线程
//PARA: char* threadname 线程名
// void(*fun)(void*) 线程回调函数
// void* para 回调函数入口参数
Void Reg_Thread(char* threadname, void(*fun)(void*), void* para);
 
//FUNCTION:注销一个线程
// PARA: char* threadname 线程名
Void Unreg_Thread(char* threadname);
这两个接口简单明了,我可以写我的低一个线程了:
#define FREE(s) do{/
free(s);/
s = null;/
}while(0)
Void fun(void*para)
{
……
   while(true)
   {
     DoSomething()
}
}
Void *para = malloc(1<<10);
Reg_Thread(“偶的线程”, fun, para);
……
if(我想停了这个线程)
Unreg_Thread(“偶的线程”);
FREE(para);
……
的确你的线程启动,或许也实现了你的某些目的,但是注意,你的代码有了隐患,这个隐患就是来自于那个注销语句。“注销? Unreg_Thread(“偶的线程”);?”是的,一个强型的线程注销可能会带来资源的泄漏。
 
 
Void fun(void*para)
{
void *p = malloc(1<<10);//例如你这里申请了一段资源
    while(true)
    {
      DoSomething()
}
FREE ( p );//这里释放
 
}
虽然你很小心,你的那一开的内存确实随着线程的消亡丢了!因为系统提供的注销线程的API一般不会保证你的函数会正常的执行到底,事实上,上面这段代码也没有底,你那是个死循环,转不出来了。说到这里我想说明一点:
²         程序中的资源有两种:一种是由系统替你管的,他的生死轮回你不用负责,一种是系统借给你的,借人东西用完要还的,而且要完完整整的还,这是最最基本的品格。
注: 这里的资源包括你动态申请的内存,你申请的信号量,你创建的线程。
如果你向上面那样做,就会丢掉人家的东西,就会让自己丧失某种品格,怎么办?修改方案吧!修改及添加代码如下:
 
bool g_blFlag = true; //加个全局变量
#define Unreg_Thread(s) g_blFlag = false //加个宏替换掉系统给我的那个让我丧失品格的API
Void fun(void*para)
{
void *p = malloc(1<<10);//例如你这里申请了一段资源
    while(g_blFlag)//用个变量控制这个循环总行了吧
    {
      DoSomething()
}
FREE ( p );//这里释放
 
}
“这下程序总算安全的把资源释放了吧,^_^,内存终于可以安全释放,俺的品格也算保住了!”
呵呵,先别高兴的太早,隐患还没有结束!
“还没有结束?”
是的,我们回过头来再去看那个注册函数吧。
……
Void *para = malloc(1<<10);
Reg_Thread(“偶的线程”, fun, para);
……
if(我想停了这个线程)
Unreg_Thread(“偶的线程”);
FREE(para);//这个时候线程真的从循环体跳出来了吗?
……
当我们把那个循环标志置成false后,我在free我申请内存的时候,偶的线程真的结束了吗?答案是:I don’t know!
是的,目前你没有办法预知这个线程到底什么时候真正退出资源竞争的舞台。我们再加点手段吧!用一个信号量来通知我们,这总可以了吧,于是我们又得到了下面的代码:
 
bool g_blFlag = true; //加个全局变量
SEMAPHORE semaphore;
#define Unreg_Thread(s) do{g_blFlag = false;/
P(semaphore);}While(0)  
Void fun(void*para)
{
   P(semaphore);
void *p = malloc(1<<10);//例如你这里申请了一段资源
    while(g_blFlag)//用个变量控制这个循环总行了吧
    {
      DoSomething()
}
FREE ( p );//这里释放
V(semaphore);
}
首先我们创建了一个互斥信号量;在一进入线程执行体就先获取该信号量,退出时释放;注销动作也会等待这个信号量,然后才往下执行。安全了吗?
“……?”嘀哒、嘀哒、嘀哒……
我们的结束线程的动作跟你的思绪一样在等待,等待什么?为什么线程还是没有退出呢?再往下看:
……
Void fun(void*para)
{
   P(semaphore);
void *p = malloc(1<<10);//例如你这里申请了一段资源
    while(g_blFlag)//用个变量控制这个循环总行了吧
    {
      DoSomething()//这里究竟是个什么动作呢?sleep(2分钟);
}
FREE ( p );//这里释放
V(semaphore);
}
如果我们的函数执行体里面存在着类似于 sleep(2分钟)这样的一个动作该怎么办呢?更可怕的是象网络程序中的select阻塞式等待信号量又该怎么办呢?在这种情况下,我们怎么做才能保证他更快点退出呢?我的结束线程的动作不能太让步啊!!
改写这个函数,是的用自己的一个函数来替代这个系统调用,我们刚才已经不知不觉的用宏做了不少事情,这回还是用这个易容手段来达到我们的目的!
适配如下:
 
Void sleep(DWORD time);//假设这个是系统提供的调用
Void mySleep(DWORD time)
{
    if(时间小于1秒)//这里是我们结束线程的等待耐心极限
{
   return sleep(time);
}
把time拆成n个1秒
do
{
    sleep(1秒);
} while((g_blFlag)&&(--n));
}
#define sleep mySleep
进行如上适配,我们用自己的函数代替了系统提供的函数,这下解决了我们的等待问题了吧,其他的也进行类似的适配就可以了^_^。
别高兴太早!隐患依旧存在:
……
Unreg_Thread(“偶的线程”);
……
sleep(2分钟);//我这里的确是想等两分钟啊!!
如果在你结束线程以后的其他地方的代码处,你还是想调用这个sleep又该怎么办呢?本想让它睡2分钟,它1秒就跑出来了啊!总不会在其他调用前的地方都加个 g_blFlag=true;来控制它吧!怎么办?一阵手忙脚乱后又来了灵感。
 
#define sleep(s) if( g_blSleepFlag) /   // 再添加一个变量用于控制这个调用总可以了吧!
mySleep(s);/
else/
 sleep(s)
 
#define Reg_Thread g_blSleepFlag = true; Reg_Thread //这个也适配一下
 
#define Unreg_Thread(s) do{g_blFlag = false;/
P(semaphore);/
g_blSleepFlag = false;}While(0)                       
 
bool g_blSleepFlag = true;//添加这个变量
 
 
……
Unreg_Thread(“偶的线程”);
 
……
sleep(2分钟);//我回可是调的实实在在的sleep啊!!
 通过添加一个新的全局变量,又把这个小问题搞定了^_^,……安全了^_^……?
请习惯性的接下来看吧!
Reg_Thread(“偶的线程1”, fun, para);
Reg_Thread(“偶的线程2”, fun, para);
……
if(我想停了这个线程1)
Unreg_Thread(“偶的线程1”);//我仅仅是想结束线程1但是糟糕的是线程2也退出了啊!!!!
……
如果你注册类似的两个线程,当你结束其中的一个线程的时候,另一个也随着结束,但是另一个可能又是个阻塞,完了全乱套了啊#◎¥%◎#×……%※
现在有一种感受叫做挣扎,怎么样,滋味不好受啊!究竟是哪个环节出了问题让我掉进了泥潭越陷越深呢?平日里我们经常听到的:
²         写代码,写可重用代码。
为何我的这段充满了小技巧的代码可复用性可扩展性如此的差,随便一点缺陷就要伤筋动骨……
抱怨至此打住吧!我们继续往下看!
Class MyThread
{
public:
 void Run();
 void Stop();
private :
bool m_blFlag;
SEMAPHORE semaphore;
private:
virtual void PreThread();
virtual void OnThread() = 0;
virtual void PostThread();
Void sleep(DWORD time)
{
    if(时间小于1秒)//这里是我们结束线程的等待耐心极限
{
   return ::sleep(time);
}
把time拆成n个1秒
do
{
    ::sleep(1秒);
} while((m_blFlag)&&(--n));
}
 
 static void func(void*para)
{
 MyThread* pThis = static_cast< MyThread* >( para);
  If(null == pThis)
 {
    return;
}
pThis-> PreThread();
while(pThis ->m_blFlag)
    {
      pThis -> OnThread ();
}
pThis-> PostThread();
 
}
};
我们把它封装成一个类的架子,以面向对象的方式去启停一个线程,它里面的具体实现留待感兴趣的读者去自己尝试着填写吧!你想起什么样的任务,就由着你派生吧,你想再适配什么阻塞函数,就随你适配吧。至少在这一节我实在是不想说它还有隐患了,因为要休息一下了^_^
总结:感觉说到头,我在试图说服一个c程序员改用c++^_^
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值