一雨田的专栏

伟人将复杂的事情变简单,小人将简单的事情变复杂

用户操作
[即时聊天] [发私信] [加为好友]
一雨田ID:dylgsy
91678次访问,排名1090,好友2人,关注者13人。
一雨田
dylgsy的文章
原创 41 篇
翻译 1 篇
转载 6 篇
评论 292 篇
最近评论
heray818:我的邮箱是:heray818@126.com
heray818:你好 能否也传我一份 谢谢了啊
救援隊募集:アダルトエロ不倫
モテ度審査員:童貞セフレナンパ
temp:很好
文章分类
收藏
    相册
    好友Blog
    圈内朋友
    sankt的专栏
    存档
    软件项目交易
    订阅我的博客
    XML聚合  FeedSky
    订阅到鲜果
    订阅到Google
    订阅到抓虾
    订阅到BlogLines
    订阅到Yahoo
    订阅到GouGou
    订阅到飞鸽
    订阅到Rojo
    订阅到newsgator
    订阅到netvibes

    原创 Windows 线程漫谈——界面线程和工作者线程收藏

    新一篇: 我的计算机历程 | 旧一篇: VC Debug 小技巧——伪符号

    每个系统都有线程,而线程的最重要的作用就是并行处理,提高软件的并发率。针对界面来说,还能提高界面的响应力。

     线程分为界面线程和工作者线程,界面实际就是一个线程画出来的东西,这个线程维护一个“消息队列”,“消息队列”也是界面线程和工作者线程的最大区别,这个词应该进到你的脑子里,根深蒂固的!

    如果在界面线程的某个地方停住,这说明它处理不了窗口消息了,所以有时候我们就会看到整个界面无响应了。这种问题后面会提供一个叫 WaitForObjectEx 的函数来解决,我们后面再谈。

    线程首先就是它的创建,创建是用下面这个函数:CreateThread; 具体的参数我不说了,自己查MSDN。其中的 Thread1 是线程函数。线程函数是一个全局函数,如下:

    DWORD WINAPI Thread1(LPVOID lpParam)
    {
      while(1)
     {
      OutputDebugString("11111");

      Sleep(10);
     }
     return 0;
    }

    // 下面这一句是创建线程
    CreateThread(NULL, 0, Thread1, 0, 0, NULL);

    当然我们不能让一个线程自生自灭,那样有可能在你退出程序的时候出现一些莫名其妙的问题,或者丢失一些数据,或者给你弹一个崩溃的对话框等等。。。

    所以我们就要对这个线程进行管理,首先就是让它退出。

    我们给它的while加上一个 BOOL 变量 g_bExitThread的判断,这样的话,线程函数就变成下面这样:

    DWORD WINAPI Thread1(LPVOID lpParam)
    {
      while(!g_bExitThread)
     {
      OutputDebugString("11111");

      Sleep(10);
     }
     return 0;
    }

    然后在需要它退出的时候把g_bExitThread设为TRUE,表示,喂,兄弟,你该退出了。

    当然我们还要知道它是否成功退出了,因为线程句柄是一个内核对象,所以我们就要用到Windows的WaitForSingleObject来等待了。创建的时候和等待它退出的代码就要改变了,多了一个 HANDLE g_hTrd的变量:

    // 创建
    g_bExitThread = FALSE;
    g_hTrd = CreateThread(NULL, 0, Thread1, 0, 0, NULL);

    // 等待线程结束
    g_bExitThread = TRUE;

     if(g_hTrd != NULL)
     {
      DWORD dwRet = WaitForSingleObject(g_hTrd, 5000);
      if(dwRet == WAIT_OBJECT_0)
      {
       AfxMessageBox("Thread exit success!");
      }
      else
      {
       DWORD dwRet = 0;
       GetExitCodeThread(g_hTrd, &dwRet);
       TerminateThread(g_hTrd, dwRet);
       AfxMessageBox("Thread exit, but not all ok!");
      }
      CloseHandle(g_hTrd);
      g_hTrd = NULL;
     }

    上面说了在界面线程里等待别的线程结束,也就是使用 WaitForSingleObject 的时候会阻塞整个窗口消息的处理,所以我们如果在界面线程里要等待别的内核对象时,我们要采用这种“等一下,处理一下界面消息”的方法。我已经写好了一个 WaitForObjectEx 的函数,如下:

    // 此函数只能用于界面线程
    static DWORD WaitForObjectEx( HANDLE hHandle, DWORD dwMilliseconds )
    {
     BOOL bRet;
     MSG msg;
     INT iWaitRet;
     int nTimeOut = 0;
     while( (bRet = ::GetMessage( &msg, NULL, 0, 0 )) != 0)
     {
      if(nTimeOut++ * 20 >= dwMilliseconds)
       break;

      iWaitRet = WaitForSingleObject(hHandle, 20);
      if(iWaitRet != WAIT_TIMEOUT)
      {
       break;
      }
      if (bRet == -1)
      {
       break;
      }
      else
      {
       ::TranslateMessage(&msg);
       ::DispatchMessage(&msg);
      }
     }

     return iWaitRet;
    }

    很多时候,我们不想把线程作为一个全局函数来使用,所以这个时候我们把线程作为一个类的静态成员对象来写。当然也不能少了刚才的两个变量:退出标志和线程句柄。(设这个类是CTestThreadDlg)

    // H 文件 
    BOOL m_bExitThread;
     HANDLE m_hTrd;
     static DWORD WINAPI Thread1(LPVOID lpParam);

    // CPP文件,创建的时候把 this 指针传进去,因为类静态成员函数不能访问类的非静态成员,没有this指针
    //(C++的知识点)
     m_bExitThread = FALSE;
     m_hTrd = CreateThread(NULL, 0, Thread1, this, 0, NULL);

    线程函数变成了:

     DWORD WINAPI CTestThreadDlg::Thread1(LPVOID lpParam)
     {
      CTestThreadDlg *pDlg = (CTestThreadDlg*)lpParam;
      while(!pDlg->m_bExitThread)
      {
       OutputDebugString("11111");
      
       Sleep(10);
      }
      return 0;
     }

    当有几个线程一起跑的时候,我们就要注意线程的同步问题了,线程的同步一般来说,是在多个线程共用了资源的时候。比如两个线程都用到了同一个VECTOR,都对VECTOR进行插入操作,不幸的是,VECTOR不是线程安全的,这个时候程序就会崩溃,所以我们就要对VECTOR这个资源做同步,同步的意思是“我访问的时候,你等待”。程序大致如下:

    DWORD WINAPI CTestThreadDlg::Thread1(LPVOID lpParam)
     {
      CTestThreadDlg *pDlg = (CTestThreadDlg*)lpParam;
      while(!pDlg->m_bExitThread)
      {
       OutputDebugString("11111");
     
       pDlg->m_csForVec.Lock();
       pDlg->m_vecTest.push_back("111");
       pDlg->m_csForVec.Unlock();
     
       Sleep(10);
      }
      return 0;
     }

    DWORD WINAPI CTestThreadDlg::Thread2(LPVOID lpParam)
    {
     CTestThreadDlg *pDlg = (CTestThreadDlg*)lpParam;
     while(!pDlg->m_bExitThread2)
     {
      OutputDebugString("222");

      pDlg->m_csForVec.Lock();
      pDlg->m_vecTest.push_back("222");
      pDlg->m_csForVec.Unlock(); 

      Sleep(10);
     }
     return 0;
    }

    m_csForVec 是一个CCriticalSection变量,这个同步对象和其他的同步变量(事件、信号量、互斥区等)有一些不一样,例如只能在同一个进程的线程间访问、在操作系统的用户态访问,其他的必须进入核心态。所以这样导致了这种关键区的核心对象的速度要比其他的快100倍左右。。。

    上面已经说了线程的创建、管理(退出线程、等待线程)、同步等,那我们发现了什么共性呢?作为一个程序员,我们要很敏感的发现这些代码上的共性,这是我们设计代码的主要前提。

    首先我们发现上面的线程都有两个变量: 
    BOOL m_bExitThread;  // 让线程退出的标志
     HANDLE m_hTrd;  // 线程句柄

    另外我们WaitForSingleObject 的时候不能无限等待,所以要多一个 DWORD m_dwWaitTimeOut;

    由于我想把线程启动和结束封装起来,所以我设计了这几个接口:

     BOOL Start(LPVOID lpParam);  //  启动线程,线程所需要的参数从这里传进
     BOOL End(); // 结束线程
     virtual void Run(); // 重写Run函数

    所以整个的线程封装成以下的类:

    // MyThread.h

    #ifndef MY_THREAD_H
    #define MY_THREAD_H

    class CMyThread
    {
    public:
     CMyThread();
     virtual ~CMyThread();

     BOOL Start(LPVOID lpParam);
     BOOL End();
     virtual void Run();

    protected:
     static DWORD WINAPI Thread(LPVOID lpParam);
     void RunOnceEnd();

     DWORD m_dwWaitTimeOut;
     BOOL m_bExitThread;
     HANDLE m_hTrd;
     LPVOID m_lpParam;
    };

    #endif

    // MyThread.Cpp

    #include "stdafx.h"
    #include "MyThread.h"
    /////////////////////////////////////////////////////////////////////////////
    // CMyThread
    CMyThread::CMyThread()
    {
     m_bExitThread = FALSE;
     m_hTrd = NULL;
     m_dwWaitTimeOut = 5000;
    }

    CMyThread::~CMyThread()
    {

    }

    BOOL CMyThread::Start(LPVOID lpParam)
    {
     m_lpParam = lpParam;
     m_bExitThread = FALSE;
     m_hTrd = CreateThread(NULL, 0, Thread, this, 0, NULL);

     return TRUE;
    }

    BOOL CMyThread::End()
    {
     m_bExitThread = TRUE;

     if(m_hTrd != NULL)
     {
      DWORD dwRet = WaitForSingleObject(m_hTrd, m_dwWaitTimeOut);
      if(dwRet == WAIT_OBJECT_0)
      {
       AfxMessageBox("Thread exit success!");
      }
      else
      {
       DWORD dwRet = 0;
       GetExitCodeThread(m_hTrd, &dwRet);
       TerminateThread(m_hTrd, dwRet);
       AfxMessageBox("Thread fucking exit!");
      }

      CloseHandle(m_hTrd);
      m_hTrd = NULL;
     }
     
     return TRUE;
    }

    DWORD WINAPI CMyThread::Thread(LPVOID lpParam)
    {
     CMyThread *pTrd = (CMyThread *)lpParam;
     
     while(!pTrd->m_bExitThread)
     {
      pTrd->Run();
     }

     return 0;
    }

    void CMyThread::RunOnceEnd()
    {
     m_bExitThread = TRUE;
     CloseHandle(m_hTrd);
     m_hTrd = NULL;
    }

    void CMyThread::Run()
    {
    }

    我们需要写我们自己的线程的时候就重载一下这个Run函数

    // 派生出一个类
    class CMyThread1 : public CMyThread
    {
    public:
     virtual void Run();
    };

    // 改写Run函数
    void CMyThread1::Run()
    {
     CTestThreadDlg *pDlg = (CTestThreadDlg *)m_lpParam;

     OutputDebugString("222");
     
     pDlg->m_csForVec.Lock();
     pDlg->m_vecTest.push_back("222");
     pDlg->m_csForVec.Unlock(); 
     
     Sleep(10);

     // 如果此线程只想运行一次,加上下面这句
     RunOnceEnd();
    }

    然后我们之前的两个线程的使用就变成了下面的形式:

    CMyThread1 g_t1, g_t2, g_t3;
    void CTestThreadDlg::OnButton3()
    {
     g_t1.Start(this);
     g_t2.Start(this);
     g_t3.Start(this); 
    }

    void CTestThreadDlg::OnButton4()
    {
     g_t1.End();
     g_t2.End();
     g_t3.End();  
    }


    只需要以下几步:
    1、派生自己的线程类
    2、重载Run函数
    3、调用Start启动线程
    4、调用End结束线程

    当然这种封装方式是我自己喜欢的,封装的目的是方便使用,隐藏细节,诸位看官也可以根据自己的喜好,封装线程的使用方法,如果能在此公开一下你的成果,让我和大家都学习一下你的设计手法,那就真是very good and 3q了!

     

    发表于 @ 2008年03月13日 10:13:00|评论(loading...)|编辑

    新一篇: 我的计算机历程 | 旧一篇: VC Debug 小技巧——伪符号

    评论

    #qzmguy 发表于2008-03-13 15:07:52  IP: 222.240.167.*
    好文章,学习了。转载一下啊
    #hzyong_c 发表于2008-03-17 11:34:36  IP: 219.142.142.*
    嘿嘿,学习了,特别是WaitForObjectEx函数
    #dylgsy 发表于2008-03-31 12:36:51  IP: 211.156.179.*
    To qzmguy:

    呵呵,欢迎转载!

    To hzyong_c:

    对界面线程的处理往往是我们比较容易忽略的,一不小心,界面就不能响应了。

    我觉得这里比较好的心得还有那个线程类的封装,提取共性,程序员最重要的思想。
    #D_MM 发表于2008-04-07 23:22:13  IP: 59.41.157.*
    靓仔,好仰慕你啊,有空来看我哦。
    发表评论  


    登录
    Csdn Blog version 3.1a
    Copyright © 一雨田