VC多线程编程学习笔记(一)
最近两天在学多线程编程,有了一些心得,写下来和大家一起共勉。文中一些部分引用了韩耀旭的文章《多线程编程》http://www.vckbase.com/document/viewdoc/?id=1704和MSDN资料。
一、缘起
工作上要用到串口编程,本来一直是用mscomm控件来进行串口通讯的,后来觉得这个控件功能不灵活,想直接使用api编程,那就不可避免的要使用多线程技术:用一个支线程一直挂在那里监听串口,就不影响主线程的消息循环了。
二、为何要用多线程
有时候需要把程序的运行挂起一段时间,我们会用到
Sleep(5000);
来让程序挂起5秒钟,而这个时候,程序在这5秒钟里就“死”在那里了,不再响应其他任何消息。
或者有时候会用一个
While(1)
这样一个死循环去完成一些监听工作,但这样的话,主界面也会“死”在那里。
这些也许都不是程序员的本意,那我们可以使用多线程技术,把一些耗时的操作放到支线程里去,而不影响主线程的消息循环。
三、相关函数
1. HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes,DWORD dwStackSize,LPTHREAD_START_ROUTINE lpStartAddress,LPVOID lpParameter,DWORD dwCreationFlags,LPDWORD lpThreadID );
该函数在其调用进程的进程空间里创建一个新的线程,并返回已建线程的句柄,其中各参数说明如下:
如果创建成功则返回线程的句柄,否则返回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强行终止某一线程的执行。各参数含义如下:
使用TerminateThread()终止某个线程的执行是不安全的,可能会引起系统不稳定;虽然该函数立即终止线程的执行,但并不释放线程所占用的资源。因此,一般不建议使用该函数。
6. BOOL PostThreadMessage(DWORD idThread,UINT Msg,WPARAM wParam,LPARAM lParam);
该函数将一条消息放入到指定线程的消息队列中,并且不等到消息被该线程处理时便返回。
调用该函数时,如果即将接收消息的线程没有创建消息循环,则该函数执行失败。
7. DWORD WaitForSingleObject(HANDLE hHandle,DWORD dwMilliseconds);
四、两个重要元素
1. Volatile
volatile 修饰符的作用是告诉编译器无需对该变量作任何的优化,即无需将它放到一个寄存器中,并且该值可被外部改变。对于多线程引用的全局变量来说,volatile 是一个非常重要的修饰符。
2. 死锁
多线程编程中,会出现多个线程都需要使用同一个资源,这时,为了保证数据同步,我们可以使用很多种方法,在线程1使用某个资源的时候,锁住它,而此时,线程2也申请使用此资源时,就只能等待,直到线程1释放了这个资源,这是很重要的,但如果不注意,若线程1正在使用资源1,而线程2正在使用资源2,而双方都申请了对方所锁住的资源,则发生了死锁,互相申请不到,结果两个线程都“死”在了那里。
五、一个简单的使用多线程技术的小对话框(使用mfc)
在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对象是个线程同步对象,用来在线程之间发送信息,我会在下面的学习笔记中阐述用法。