VC多线程编程学习笔记

1. HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
DWORD dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadID );
该函数在其调用进程的进程空间里创建一个新的线程,并返回已建线程的句柄,其中各参数说明如下:
lpThreadAttributes:指向一个 SECURITY_ATTRIBUTES 结构的指针,该结构决定了线程的安全属性,一般置为 NULL;
dwStackSize:指定了线程的堆栈深度,一般都设置为0;
lpStartAddress:表示新线程开始执行时代码所在函数的地址,即线程的起始地址。一般情况为(LPTHREAD_START_ROUTINE)ThreadFunc,ThreadFunc 是线程函数名;
lpParameter:指定了线程执行时传送给线程的32位参数,即线程函数的参数;
dwCreationFlags:控制线程创建的附加标志,可以取两种值。如果该参数为0,线程在被创建后就会立即开始执行;如果该参数为CREATE_SUSPENDED,则系统产生线程后,该线程处于挂起状态,并不马上执行,直至函数ResumeThread被调用;
lpThreadId:该参数返回所创建线程的ID;
如果创建成功则返回线程的句柄,否则返回NULL。

2. DWORD SuspendThread(HANDLE hThread);
该函数用于挂起指定的线程,如果函数执行成功,则线程的执行被终止。

3. DWORD ResumeThread(HANDLE hThread);
该函数用于结束线程的挂起状态,执行线程。

4. VOID ExitThread(DWORD dwExitCode);
该函数用于线程终结自身的执行,主要在线程的执行函数中被调用。其中参数dwExitCode用来设置线程的退出码。

5. BOOL TerminateThread(HANDLE hThread,DWORD dwExitCode);
一般情况下,线程运行结束之后,线程函数正常返回,但是应用程序可以调用TerminateThread强行终止某一线程的执行。各参数含义如下:
hThread:将被终结的线程的句柄;
dwExitCode:用于指定线程的退出码。
使用TerminateThread()终止某个线程的执行是不安全的,可能会引起系统不稳定;虽然该函数立即终止线程的执行,但并不释放线程所占用的资源。因此,一般不建议使用该函数。

6. BOOL PostThreadMessage(DWORD idThread,UINT Msg,WPARAM wParam,LPARAM lParam);
该函数将一条消息放入到指定线程的消息队列中,并且不等到消息被该线程处理时便返回。
idThread:将接收消息的线程的ID;
Msg:指定用来发送的消息;
wParam:同消息有关的字参数;
lParam:同消息有关的长参数;
调用该函数时,如果即将接收消息的线程没有创建消息循环,则该函数执行失败。

7. DWORD WaitForSingleObject(HANDLE hHandle,DWORD dwMilliseconds);
hHandle为要监视的对象(一般为同步对象,也可以是线程)的句柄;
dwMilliseconds为hHandle对象所设置的超时值,单位为毫秒;(有两个特殊值:0,则函数立即返回,若为INFINITE,则函数会一直挂起,直到hHandle所指向的对象变为有信号状态。)

两个重要元素
1. Volatile
volatile 修饰符的作用是告诉编译器无需对该变量作任何的优化,即无需将它放到一个寄存器中,并且该值可被外部改变。对于多线程引用的全局变量来说,volatile 是一个非常重要的修饰符。
2. 死锁
多线程编程中,会出现多个线程都需要使用同一个资源,这时,为了保证数据同步,我们可以使用很多种方法,在线程1使用某个资源的时候,锁住它,而此时,线程2也申请使用此资源时,就只能等待,直到线程1释放了这个资源,这是很重要的,但如果不注意,若线程1正在使用资源1,而线程2正在使用资源2,而双方都申请了对方所锁住的资源,则发生了死锁,互相申请不到,结果两个线程都“死”在了那里。

一个简单的使用多线程技术的小对话框(使用mfc)
在vc中新建一个对话框程序,布局如图:

4dbbf76f16163385fba4b&690
VC多线程编程学习笔记

1.在对话框的.cpp文件中加入以下两句:
#include “afxmt.h” //为了使用事件对象CEvent ,所需的头文件
CEvent event1; //事件对象

2.加入两个全局函数作为两个支线程的入口函数:

UINT proc1(LPARAM lParam)
{
  CEdit* pEdit = (CEdit*)lParam;
  CString str;
  int i=1;
  while (1)
  {
    str.Format("第%d次刷新",i);
    pEdit->SetWindowText(str);
    if (i%5==0)
    {
      event1.SetEvent();
    }
    i++;
    Sleep(1000);
  }
}

UINT proc2(LPARAM lParam)
{
  CEdit* pEdit = (CEdit*)lParam;
  CString str;
  CTime time;
  while (1)
  {
    WaitForSingleObject(event1.m_hObject,INFINITE);
    time = CTime::GetCurrentTime();
    str = time.Format("%H : %M : %S");
    pEdit->SetWindowText(str);
  }
}

3.为两个只读的文本框添加控件变量 m_edit1 ,m_edit2
4.为按钮添加单击事件函数:
void CExampleMultiThreadDlg::OnStart()
{
HANDLE thread1 = CreateThread(NULL,
0,
(LPTHREAD_START_ROUTINE)proc1,
&m_edit1,
0,
NULL);
HANDLE thread2 = CreateThread(NULL,
0,
(LPTHREAD_START_ROUTINE)proc2,
&m_edit2,
0,
NULL);
}
5.运行后,点击开始按钮,两个分线程便开始工作了,线程1会每秒钟刷新一次第一个对话框,每刷新5次,线程2就在第二个对话框中刷新一下当前时间。
程序中用到的CEvent对象是个线程同步对象,用来在线程之间发送信息,会在下面的学习笔记中阐述用法。

MFC对多线程的支持
MFC中有两种线程:工作者线程和用户界面线程,前者没有消息循环,后者有自己的消息循环。
我们可以使用全局函数AfxBeginThread()来创建并初始化一个线程的运行,该函数有两种重载形式(直接摘自MSDN,一部分内容做了翻译,):

CWinThread* AfxBeginThread(
AFX_THREADPROC pfnThreadProc,
LPVOID pParam,
int nPriority = THREAD_PRIORITY_NORMAL,
UINT nStackSize = 0,
DWORD dwCreateFlags = 0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );

CWinThread* AfxBeginThread(
CRuntimeClass* pThreadClass,
int nPriority = THREAD_PRIORITY_NORMAL,
UINT nStackSize = 0,
DWORD dwCreateFlags = 0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );

Return Value
返回一个指向新线程的指针。
Parameters
pfnThreadProc
指向一个工作者线程的执行函数的指针,不能为NULL,线程函数原型必须为:
UINT MyControllingFunction( LPVOID pParam );
pThreadClass
一个从继承自CWinThread的运行时类对象的指针。这个类定义了被创建的用户线程的启动,运行和退出等,拥有自己的消息循环。
pParam
传给线程函数的一个32位的参数。可以是数值,可以是指向结构的指针,也可以被忽略。
nPriority
新线程的优先级,如果为0的话,则和父线程同级。
nStackSize
新线程的堆栈大小(byte),若0,则和父线程相同。
dwCreateFlags
如果为0,则线程在创建后立刻开始执行。如果为CREATE_SUSPEND,则线程在创建后立刻被挂起;
lpSecurityAttrs
指向一个SECURITY_ATTRIBUTES结构,来指定新线程的安全属性,如果为NULL,则与父线程相同。
Remarks
Call this function to create a new thread. The first form of AfxBeginThread creates a worker thread. The second form creates a user-interface thread.

AfxBeginThread 函数创建了一个新的CWinThread对象,调用了CWinThread的CreateThread函数,开始执行线程,并返回线程指针。
既然最后还是调用了CWinThread::CreateThread函数,我们再来看一下这个函数:
BOOL CreateThread(
DWORD dwCreateFlags = 0,
UINT nStackSize = 0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );

Return Value
创建成功返回非0,失败返回0
Parameters
参数的意义和上面两个函数一样。

一般情况下,调用AfxBeginThread()来一次性地创建并启动一个线程,但是也可以通过两步法来创建线程:首先创建CWinThread类的一个对象,然后调用该对象的成员函数CreateThread()来启动该线程。

virtual BOOL CWinThread::InitInstance();
重载该函数以控制用户界面线程实例的初始化。初始化成功则返回非0值,否则返回0。用户界面线程经常重载该函数,工作者线程一般不使用InitInstance()。
virtual int CWinThread::ExitInstance();
在线程终结前重载该函数进行一些必要的清理工作。该函数返回线程的退出码,0表示执行成功,非0值用来标识各种错误。同InitInstance()成员函数一样,该函数也只适用于用户界面线程。

附:
一个简单的使用mfc多线程技术的小例子(使用用户界面线程):
1. 创建一个基于对话框的mfc程序UIThread
2. 新加入一个对话框IDC_UIThreadDlg,关联类为CUITDlg,上面放一个按钮,单击事件函数为:
AfxMessageBox(“单击了用户界面线程对话框上的按钮”);
3. 新建一个线程类CUIThread 继承CWinThread类,在类中重载两个函数

BOOL CUIThread::InitInstance()
{
m_dlg.Create(IDD_THREADDLG);
m_dlg.ShowWindow(SW_SHOW);
return TRUE;
}

int CUIThread::ExitInstance()
{
m_dlg.DestroyWindow();
return CWinThread::ExitInstance();
}

4. 在主对话框中放一个按钮,加入单击事件,里面只需添加一句:
CWinThread *pThread = AfxBeginThread(RUNTIME_CLASS(CUIThread));
5.最后别忘了在每个类中加入所关联类的头文件,编译即可运行。运行后,每点击一次主对话框上的按钮,便会启动一个支线程,而这个支线程会新建一个对话框并显示。而点击这个支线程对话框上的按钮后,另会有显示“单击了用户界面线程对话框上的按钮”的对话框跳出,充分说明了这个支线程拥有自己的消息循环。

声明:本文采用 BY-NC-SA 协议进行授权,本文链接:VC多线程编程学习笔记

发表评论