利用自定义消息在VC内进行线程间通讯

来源:互联网 发布:acrobat xi pro mac 编辑:程序博客网 时间:2024/05/16 14:21

 

 

      当我们在开发需要实时采集数据和实时显示处理结果的程序时,通常会采用多线程。通过采用多线程,使采集数据的任务和显示处理结果的任务各自独立运行,例如通过从线程采集数据,通过主线程显示处理结果,可以达到程序的实时性要求,但是前提是必须实现主线程和从线程之间的实时通讯。最常用的办法是通过共享存储区,使主、从线程都能够实时地存取资源,但是这种方法不够灵活,并会出现资源共享冲突。本文要介绍的方法正是在其基础之上,通过自定义消息来实现主、从线程之间的灵活通讯,并采用临界区来解决共享存储区的存取冲突。下面将结合在Visual C++中的一个实例对该方法进行详细介绍。

       、创建主线程界面

      1.在Visual C++开发环境下新建一个基于对话框的MFC程序,取项目名ThreadSample,为对话框添加六个编辑框控件,且将IDOK的标题改为“开始”,将IDCANCEL的标题改为“停止”并使其失效。 如下图所示:

例程界面

  2.为每个编辑框控件添加保护型成员变量,如下表所示。

控件ID

成员变量类型

成员变量名称

IDC_EDIT1

CString

m_sData1

IDC_EDIT2

int

m_iData2

IDC_EDIT3

double

m_dData3

IDC_EDIT4

CString

m_edit1

IDC_EDIT5

int

m_edit2

IDC_EDIT6

double

m_edit3

、定义实现主、从线程通讯的消息和数据结构

1.定义从线程发给主线程的消息:

#define MESSAGEFROMTHREAD   (WM_USER+1001)

2.定义主线程发给从线程的启动和停止消息:

#define STARTTHREAD              (WM_USER+1002)// 启动

#define STOPTHREAD         (WM_USER+1003)// 停止

3.定义主、从线程需要处理的数据结构,它是主、从线程共同需要的各种数据类型的集合,本例中定义如下:

struct THREADDATA

{

       CString  sData1;

       int          iData2;

       double   dData3;

};

三、创建从线程

1.在类CThreadSampleDlg的头文件中添加下面公有成员变量:

HANDLE m_hThread;

DWORD m_ThreadID;

2.在类CThreadSampleDlgOnInitDialog()成员函数中创建从线程,代码如下:

m_hThread=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)Thread_Fun, (LPVOID)this->m_hWnd, NULL, &m_ThreadID);

       if ((m_hThread == NULL))

       { 

MessageBox("从线程创建失败!", NULL, NULL);

              exit(-1);

       }

       3.定义从线程函数,代码和注释如下:

UINT Thread_Fun(void* hWnd);// 声明从线程函数

UINT Thread_Fun(void* hWnd)

{

       bool b_run_or_stop = false;//是否启动数据处理

       THREADDATA thread_data;//线程要处理的数据

       MSG msg;//线程的消息

       while(1)

       {

              if(b_run_or_stop)//如果启动数据处理

              {

                     EnterCriticalSection(&cs);//进入临界区

                     thread_data.sData1.MakeReverse();//处理数据

                     thread_data.iData2++; //本例只是对数据进行简单处理

                     thread_data.dData3+=0.2; //其原理同样适于复杂的数据处理过程

                     LeaveCriticalSection(&cs);//离开临界区

::PostMessage((HWND) hWnd, MESSAGEFROMTHREAD, (WPARAM) &thread_data, (LPARAM) 0 );//数据处理完毕,通知主线程

              }

              if(::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))

              {//检查线程的消息队列,是否有下面的自定义消息

                     switch(msg.message)

                     {

                     case WM_QUIT://退出从线程

                            ExitThread(10);

                     case STARTTHREAD:

//启动数据处理,从线程从消息中提取其将要处理的数据

                            thread_data.sData1 = ( (THREADDATA*) msg.wParam )->sData1;

                            thread_data.iData2 = ( (THREADDATA*) msg.wParam )->iData2;

                            thread_data.dData3 = ( (THREADDATA*) msg.wParam )->dData3;

                            b_run_or_stop = true;

                            break;

                     case STOPTHREAD://暂停数据处理

                            b_run_or_stop = false;

                            break;

                     default:

                            break;

                     }

              }

       }

       return 0;

}

四、添加主线程的消息处理函数

1.为“开始”和“停止”分别添加处理函数,代码入下

void CThreadSampleDlg::OnStart()

{//给线程发启动消息,传送其需要的数据,并使其启动数据处理过程

       UpdateData(true);

       GetDlgItem(IDCANCEL)->EnableWindow(true);//激活停止按钮

       GetDlgItem(IDOK)->EnableWindow(false); //使开始按钮失效

       // m_Data为头文件中定义的THREADDATA类型成员变量

       m_Data.sData1=m_sData1;

       m_Data.iData2=m_iData2;

       m_Data.dData3=m_dData3;

::PostThreadMessage(m_ThreadID, STARTTHREAD,  (WPARAM) &m_Data,  0);// m_Data的地址作为参数发给从线程

}

void CThreadSampleDlg::OnStop()

{//给线程发暂停消息,使其暂停数据处理过程

       GetDlgItem(IDCANCEL)->EnableWindow(false); //使停止按钮失效

       GetDlgItem(IDOK)->EnableWindow(true); //激活开始按钮

       ::PostThreadMessage(m_ThreadID, STOPTHREAD, 0, 0);

}

2.为来自从线程的自定义消息添加消息处理函数:

首先在类CThreadSampleDlg的头文件的AFX_MSG宏中,添加函数声明:

//{{AFX_MSG(CThreadSampleDlg)

……

afx_msg void OnMessageFromThread(WPARAM wParam, LPARAM lParam);

……

//}}AFX_MSG

再在类CThreadSampleDlg的实现文件的BEGIN_MESSAGE_MAP宏中添加消息映射

//{{AFX_MSG_MAP(CThreadSampleDlg)

……

ON_MESSAGE(MESSAGEFROMTHREAD,OnMessageFromThread)

……

//}}AFX_MSG_MAP

最后添加消息处理函数的实现代码:

void CThreadSampleDlg::OnMessageFromThread(WPARAM wParam, LPARAM lParam)

{//主线程从MESSAGEFROMTHREAD消息的参数中提取经过处理的数据

       m_edit1 = ( (THREADDATA*)wParam )->sData1;

       m_edit2 = ( (THREADDATA*)wParam )->iData2;

       m_edit3 = ( (THREADDATA*)wParam )->dData3;

       UpdateData(false);//实时更新显示经过处理的数据

}

五、采用临界区解决资源的共享冲突

由于主线程在收到MESSAGEFROMTHREAD消息后要读取参数wParam(事实上是从线程的THREADDATA类型变量thread_data的地址),而此时从线程完全可能正在对该地址对应的数据进行处理,从而出现存取冲突。因此应该采取线程同步技术,通常用到的有临界区(Critical Section)、互斥对象(Mutex)和信号量(Semaphore)。本文将采用临界区控制资源的存取,解决共享冲突。临界区其实是轻量级的互斥变量,它对于同一进程内的多线程是更好的选择。

本例中临界区的具体实现过程如下:

首先定义一个全局的静态的临界区变量

static      CRITICAL_SECTION     cs;

然后在CThreadSampleDlg::OnInitDialog()函数中对该临界区初使化

::InitializeCriticalSection(&cs);

再将引起资源存取冲突的代码加入临界区,本例中即需将从线程函数进行数据处理的代码加入临界区(代码见上面列出的线程函数代码的下划线部分)。

最后在出现退出时销毁临界区,本例即在OnClose()函数添加代码:

::DeleteCriticalSection(&cs);

这样一来,当从线程进入临界区,对变量thread_data进行处理时,主线程就不会对其进行访问了,从而不再引起资源的存取冲突。