首页 › 月度存档 › 5 月 2014

VC++多线程编程总结

VC中多线程使用比较广泛而且实用,在网上看到的教程.感觉写的挺好.

一、问题的提出
编写一个耗时的单线程程序:
  新建一个基于对话框的应用程序SingleThread,在主对话框IDD_SINGLETHREAD_DIALOG添加一个按钮,ID为IDC_SLEEP_SIX_SECOND,标题为“延时6秒”,添加按钮的响应函数,代码如下:
void CSingleThreadDlg::OnSleepSixSecond()
{
Sleep(6000); //延时6秒
}
  编译并运行应用程序,单击“延时6秒”按钮,你就会发现在这6秒期间程序就象“死机”一样,不在响应其它消息。为了更好地处理这种耗时的操作,我们有必要学习——多线程编程。
二、多线程概述
  进程和线程都是操作系统的概念。进程是应用程序的执行实例,每个进程是由私有的虚拟地址空间、代码、数据和其它各种系统资源组成,进程在运行过程中创建的资源随着进程的终止而被销毁,所使用的系统资源在进程终止时被释放或关闭。
  线程是进程内部的一个执行单元。系统创建好进程后,实际上就启动执行了该进程的主执行线程,主执行线程以函数地址形式,比如说 main或WinMain函数,将程序的启动点提供给Windows系统。主执行线程终止了,进程也就随之终止。
  每一个进程至少有一个主执行线程,它无需由用户去主动创建,是由系统自动创建的。用户根据需要在应用程序中创建其它线程,多个线程并发地运行于同一个进程中。一个进程中的所有线程都在该进程的虚拟地址空间中,共同使用这些虚拟地址空间、全局变量和系统资源,所以线程间的通讯非常方便,多线程技术的应用也较为广泛。
  多线程可以实现并行处理,避免了某项任务长时间占用CPU时间。要说明的一点是,目前大多数的计算机都是单处理器(CPU)的,为了运行所有这些线程,操作系统为每个独立线程安排一些CPU时间,操作系统以轮换方式向线程提供时间片,这就给人一种假象,好象这些线程都在同时运行。由此可见,如果两个非常活跃的线程为了抢夺对CPU的控制权,在线程切换时会消耗很多的CPU资源,反而会降低系统的性能。这一点在多线程编程时应该注意。
  Win32 SDK函数支持进行多线程的程序设计,并提供了操作系统原理中的各种同步、互斥和临界区等操作。Visual C++6.0中,使用MFC类库也实现了多线程的程序设计,使得多线程编程更加方便。
三、Win32 API对多线程编程的支持
  Win32 提供了一系列的API函数来完成线程的创建、挂起、恢复、终结以及通信等工作。下面将选取其中的一些重要函数进行说明。
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:同消息有关的长参数;
调用该函数时,如果即将接收消息的线程没有创建消息循环,则该函数执行失败。
四、Win32 API多线程编程例程
例程1 MultiThread1
建立一个基于对话框的工程MultiThread1,在对话框IDD_MULTITHREAD1_DIALOG中加入两个按钮和一个编辑框,两个按钮的ID分别是IDC_START,IDC_STOP,标题分别为“启动”,“停止”,IDC_STOP的属性选中Disabled;编辑框的ID为IDC_TIME ,属性选中Read-only;
 
在MultiThread1Dlg.h文件中添加线程函数声明: void ThreadFunc();
注意,线程函数的声明应在类CMultiThread1Dlg的外部。在类CMultiThread1Dlg内部添加protected型变量:
HANDLE hThread;
DWORD ThreadID;
分别代表线程的句柄和ID。
 
在MultiThread1Dlg.cpp文件中添加全局变量m_bRun : volatile BOOL m_bRun;
m_bRun 代表线程是否正在运行。
你要留意到全局变量 m_bRun 是使用 volatile 修饰符的,volatile修饰符的作用是告诉编译器无需对该变量作任何的优化,即无需将它放到一个寄存器中,并且该值可被外部改变。对于多线程引用的全局变量来说,volatile是一个非常重要的修饰符。
编写线程函数:
void ThreadFunc()
{
CTime time;
CString strTime;
m_bRun=TRUE;
while(m_bRun)
{
time=CTime::GetCurrentTime();
strTime=time.Format(“%H:%M:%S”);
::SetDlgItemText(AfxGetMainWnd()->m_hWnd,IDC_TIME,strTime);
Sleep(1000);
}
}
该线程函数没有参数,也不返回函数值。只要m_bRun为TRUE,线程一直运行。
双击IDC_START按钮,完成该按钮的消息函数: void CMultiThread1Dlg::OnStart()
{
// TODO: Add your control notification handler code here
hThread=CreateThread(NULL,
0,
(LPTHREAD_START_ROUTINE)ThreadFunc,
NULL,
0,
&ThreadID);
GetDlgItem(IDC_START)->EnableWindow(FALSE);
GetDlgItem(IDC_STOP)->EnableWindow(TRUE);
}
双击IDC_STOP按钮,完成该按钮的消息函数: void CMultiThread1Dlg::OnStop()
{
// TODO: Add your control notification handler code here
m_bRun=FALSE;
GetDlgItem(IDC_START)->EnableWindow(TRUE);
GetDlgItem(IDC_STOP)->EnableWindow(FALSE);
}
编译并运行该例程,体会使用Win32 API编写的多线程。

例程2 MultiThread2
  该线程演示了如何传送一个一个整型的参数到一个线程中,以及如何等待一个线程完成处理。
建立一个基于对话框的工程MultiThread2,在对话框IDD_MULTITHREAD2_DIALOG中加入一个编辑框和一个按钮,ID分别是IDC_COUNT,IDC_START,按钮控件的标题为“开始”;
在MultiThread2Dlg.h文件中添加线程函数声明: void ThreadFunc(int integer);
注意,线程函数的声明应在类CMultiThread2Dlg的外部。
在类CMultiThread2Dlg内部添加protected型变量:
HANDLE hThread;
DWORD ThreadID;
分别代表线程的句柄和ID。
 
打开ClassWizard,为编辑框IDC_COUNT添加int型变量m_nCount。在MultiThread2Dlg.cpp文件中添加:
void ThreadFunc(int integer)
{
int i;
for(i=0;iEnableWindow(FALSE);
WaitForSingleObject(hThread,INFINITE);
GetDlgItem(IDC_START)->EnableWindow(TRUE);
}
顺便说一下WaitForSingleObject函数,其函数原型为:DWORD WaitForSingleObject(HANDLE hHandle,DWORD dwMilliseconds);
hHandle为要监视的对象(一般为同步对象,也可以是线程)的句柄;
dwMilliseconds为hHandle对象所设置的超时值,单位为毫秒;
  当在某一线程中调用该函数时,线程暂时挂起,系统监视hHandle所指向的对象的状态。如果在挂起的dwMilliseconds毫秒内,线程所等待的对象变为有信号状态,则该函数立即返回;如果超时时间已经到达dwMilliseconds毫秒,但hHandle所指向的对象还没有变成有信号状态,函数照样返回。参数dwMilliseconds有两个具有特殊意义的值:0和INFINITE。若为0,则该函数立即返回;若为INFINITE,则线程一直被挂起,直到hHandle所指向的对象变为有信号状态时为止。
  本例程调用该函数的作用是按下IDC_START按钮后,一直等到线程返回,再恢复IDC_START按钮正常状态。编译运行该例程并细心体会。

例程3 MultiThread3传送一个结构体给一个线程函数也是可能的,可以通过传送一个指向结构体的指针参数来完成。先定义一个结构体:
typedef struct
{
int firstArgu,
long secondArgu,

}myType,*pMyType;
创建线程时CreateThread(NULL,0,threadFunc,pMyType,…);
在threadFunc函数内部,可以使用“强制转换”:
int intValue=((pMyType)lpvoid)->firstArgu;
long longValue=((pMyType)lpvoid)->seconddArgu;
……
例程3 MultiThread3将演示如何传送一个指向结构体的指针参数。
建立一个基于对话框的工程MultiThread3,在对话框IDD_MULTITHREAD3_DIALOG中加入一个编辑框IDC_MILLISECOND,一个按钮IDC_START,标题为“开始”,一个进度条IDC_PROGRESS1;
打开ClassWizard,为编辑框IDC_MILLISECOND添加int型变量m_nMilliSecond,为进度条IDC_PROGRESS1添加CProgressCtrl型变量m_ctrlProgress;
在MultiThread3Dlg.h文件中添加一个结构的定义:
struct threadInfo
{
UINT nMilliSecond;
CProgressCtrl* pctrlProgress;
};
线程函数的声明: UINT ThreadFunc(LPVOID lpParam);
注意,二者应在类CMultiThread3Dlg的外部。
在类CMultiThread3Dlg内部添加protected型变量:
HANDLE hThread;
DWORD ThreadID;
分别代表线程的句柄和ID。
在MultiThread3Dlg.cpp文件中进行如下操作:
定义公共变量 threadInfo Info;
双击按钮IDC_START,添加相应消息处理函数:
void CMultiThread3Dlg::OnStart()
{
// TODO: Add your control notification handler code here
UpdateData(TRUE);
Info.nMilliSecond=m_nMilliSecond;
Info.pctrlProgress=&m_ctrlProgress;
hThread=CreateThread(NULL,
0,
(LPTHREAD_START_ROUTINE)ThreadFunc,
&Info,
0,
&ThreadID);
}
在函数BOOL CMultiThread3Dlg::OnInitDialog()中添加语句: {
……
// TODO: Add extra initialization here
m_ctrlProgress.SetRange(0,99);
m_nMilliSecond=10;
UpdateData(FALSE);
return TRUE; // return TRUE unless you set the focus to a control
}
添加线程处理函数:UINT ThreadFunc(LPVOID lpParam) {
threadInfo* pInfo=(threadInfo*)lpParam;
for(int i=0;i<100;i++) { int nTemp=pInfo->nMilliSecond;
pInfo->pctrlProgress->SetPos(i);
Sleep(nTemp);
}
return 0;
}
  顺便补充一点,如果你在void CMultiThread3Dlg::OnStart() 函数中添加语句,编译运行你就会发现进度条不进行刷新,主线程也停止了反应。什么原因呢?这是因为WaitForSingleObject函数等待子线程(ThreadFunc)结束时,导致了线程死锁。因为WaitForSingleObject函数会将主线程挂起(任何消息都得不到处理),而子线程ThreadFunc正在设置进度条,一直在等待主线程将刷新消息处理完毕返回才会检测通知事件。这样两个线程都在互相等待,死锁发生了,编程时应注意避免。

例程4 MultiThread4
该例程测试在Windows下最多可创建线程的数目。
建立一个基于对话框的工程MultiThread4,在对话框IDD_MULTITHREAD4_DIALOG中加入一个按钮IDC_TEST和一个编辑框IDC_COUNT,按钮标题为“测试”, 编辑框属性选中Read-only;
在MultiThread4Dlg.cpp文件中进行如下操作:
添加公共变量volatile BOOL m_bRunFlag=TRUE;
该变量表示是否还能继续创建线程。
添加线程函数:
DWORD WINAPI threadFunc(LPVOID threadNum)
{
while(m_bRunFlag)
{
Sleep(3000);
}
return 0;
}
只要 m_bRunFlag 变量为TRUE,线程一直运行。
双击按钮IDC_TEST,添加其响应消息函数:void CMultiThread4Dlg::OnTest()
{
DWORD threadID;
GetDlgItem(IDC_TEST)->EnableWindow(FALSE);
long nCount=0;
while(m_bRunFlag)
{
if(CreateThread(NULL,0,threadFunc,NULL,0,&threadID)==NULL)
{
m_bRunFlag=FALSE;
break;
}
else
{
nCount++;
}
}
//不断创建线程,直到再不能创建为止
m_nCount=nCount;
UpdateData(FALSE);
Sleep(5000);
//延时5秒,等待所有创建的线程结束
GetDlgItem(IDC_TEST)->EnableWindow(TRUE);
m_bRunFlag=TRUE;
}
五、MFC对多线程编程的支持
  MFC中有两类线程,分别称之为工作者线程和用户界面线程。二者的主要区别在于工作者线程没有消息循环,而用户界面线程有自己的消息队列和消息循环。
  工作者线程没有消息机制,通常用来执行后台计算和维护任务,如冗长的计算过程,打印机的后台打印等。用户界面线程一般用于处理独立于其他线程执行之外的用户输入,响应用户及系统所产生的事件和消息等。但对于Win32的API编程而言,这两种线程是没有区别的,它们都只需线程的启动地址即可启动线程来执行任务。
  在MFC中,一般用全局函数AfxBeginThread()来创建并初始化一个线程的运行,该函数有两种重载形式,分别用于创建工作者线程和用户界面线程。两种重载函数原型和参数分别说明如下:
(1) CWinThread* AfxBeginThread(AFX_THREADPROC pfnThreadProc,
LPVOID pParam,
nPriority=THREAD_PRIORITY_NORMAL,
UINT nStackSize=0,
DWORD dwCreateFlags=0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs=NULL);
PfnThreadProc:指向工作者线程的执行函数的指针,线程函数原型必须声明如下: UINT ExecutingFunction(LPVOID pParam);
请注意,ExecutingFunction()应返回一个UINT类型的值,用以指明该函数结束的原因。一般情况下,返回0表明执行成功。
pParam:传递给线程函数的一个32位参数,执行函数将用某种方式解释该值。它可以是数值,或是指向一个结构的指针,甚至可以被忽略;
nPriority:线程的优先级。如果为0,则线程与其父线程具有相同的优先级;
nStackSize:线程为自己分配堆栈的大小,其单位为字节。如果nStackSize被设为0,则线程的堆栈被设置成与父线程堆栈相同大小;
dwCreateFlags:如果为0,则线程在创建后立刻开始执行。如果为CREATE_SUSPEND,则线程在创建后立刻被挂起;
lpSecurityAttrs:线程的安全属性指针,一般为NULL;

(2) CWinThread* AfxBeginThread(CRuntimeClass* pThreadClass,
int nPriority=THREAD_PRIORITY_NORMAL,
UINT nStackSize=0,
DWORD dwCreateFlags=0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs=NULL);
pThreadClass 是指向 CWinThread的一个导出类的运行时类对象的指针,该导出类定义了被创建的用户界面线程的启动、退出等;其它参数的意义同形式1。使用函数的这个原型生成的线程也有消息机制,在以后的例子中我们将发现同主线程的机制几乎一样。
下面我们对CWinThread类的数据成员及常用函数进行简要说明。
m_hThread:当前线程的句柄;
m_nThreadID:当前线程的ID;
m_pMainWnd:指向应用程序主窗口的指针
BOOL CWinThread::CreateThread(DWORD dwCreateFlags=0,
UINT nStackSize=0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs=NULL);
  该函数中的dwCreateFlags、nStackSize、lpSecurityAttrs参数和API函数CreateThread中的对应参数有相同含义,该函数执行成功,返回非0值,否则返回0。
  一般情况下,调用AfxBeginThread()来一次性地创建并启动一个线程,但是也可以通过两步法来创建线程:首先创建CWinThread类的一个对象,然后调用该对象的成员函数CreateThread()来启动该线程。

virtual BOOL CWinThread::InitInstance();
  重载该函数以控制用户界面线程实例的初始化。初始化成功则返回非0值,否则返回0。用户界面线程经常重载该函数,工作者线程一般不使用InitInstance()。

virtual int CWinThread::ExitInstance();
  在线程终结前重载该函数进行一些必要的清理工作。该函数返回线程的退出码,0表示执行成功,非0值用来标识各种错误。同InitInstance()成员函数一样,该函数也只适用于用户界面线程。
六、MFC多线程编程实例
  在Visual C++6.0编程环境中,我们既可以编写C风格的32位Win32应用程序,也可以利用MFC类库编写C++风格的应用程序,二者各有其优缺点。基于Win32的应用程序执行代码小巧,运行效率高,但要求程序员编写的代码较多,且需要管理系统提供给程序的所有资源;而基于MFC类库的应用程序可以快速建立起应用程序,类库为程序员提供了大量的封装类,而且Developer Studio为程序员提供了一些工具来管理用户源程序,其缺点是类库代码很庞大。由于使用类库所带来的快速、简捷和功能强大等优越性,因此
除非有特殊的需要,否则Visual C++推荐使用MFC类库进行程序开发。
我们知道,MFC中的线程分为两种:用户界面线程和工作者线程。我们将分别举例说明。
用 MFC 类库编程实现工作者线程
例程5 MultiThread5
为了与Win32 API对照,我们使用MFC 类库编程实现例程3 MultiThread3。
建立一个基于对话框的工程MultiThread5,在对话框IDD_MULTITHREAD5_DIALOG中加入一个编辑框IDC_MILLISECOND,一个按钮IDC_START,标题为“开始”
,一个进度条IDC_PROGRESS1;
打开ClassWizard,为编辑框IDC_MILLISECOND添加int型变量m_nMilliSecond,为进度条IDC_PROGRESS1添加CProgressCtrl型变量
m_ctrlProgress;
在MultiThread5Dlg.h文件中添加一个结构的定义:
struct threadInfo
{
UINT nMilliSecond;
CProgressCtrl* pctrlProgress;
};
线程函数的声明:UINT ThreadFunc(LPVOID lpParam);
注意,二者应在类CMultiThread5Dlg的外部。
在类CMultiThread5Dlg内部添加protected型变量:
CWinThread* pThread;
在MultiThread5Dlg.cpp文件中进行如下操作:定义公共变量:threadInfo Info;
双击按钮IDC_START,添加相应消息处理函数:
void CMultiThread5Dlg::OnStart()
{
// TODO: Add your control notification handler code here
UpdateData(TRUE);
Info.nMilliSecond=m_nMilliSecond;
Info.pctrlProgress=&m_ctrlProgress;
pThread=AfxBeginThread(ThreadFunc,
&Info);
}
在函数BOOL CMultiThread3Dlg::OnInitDialog()中添加语句: {
……

// TODO: Add extra initialization here
m_ctrlProgress.SetRange(0,99);
m_nMilliSecond=10;
UpdateData(FALSE);
return TRUE; // return TRUE unless you set the focus to a control
}
添加线程处理函数: UINT ThreadFunc(LPVOID lpParam)
{
threadInfo* pInfo=(threadInfo*)lpParam;
for(int i=0;i<100;i++) { int nTemp=pInfo->nMilliSecond;
pInfo->pctrlProgress->SetPos(i);
Sleep(nTemp);
}
return 0;
}
用 MFC 类库编程实现用户界面线程
创建用户界面线程的步骤:
使用ClassWizard创建类CWinThread的派生类(以CUIThread类为例) class CUIThread : public
CWinThread
{
DECLARE_DYNCREATE(CUIThread)
protected:
CUIThread(); // protected constructor used by dynamic creation
// Attributes
public:
// Operations
public:
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CUIThread)
public:
virtual BOOL InitInstance();
virtual int ExitInstance();
//}}AFX_VIRTUAL
// Implementation
protected:
virtual ~CUIThread();
// Generated message map functions
//{{AFX_MSG(CUIThread)
// NOTE – the ClassWizard will add and remove member functions here.
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
重载函数InitInstance()和ExitInstance()。 BOOL CUIThread::InitInstance()
{
CFrameWnd* wnd=new CFrameWnd;
wnd->Create(NULL,”UI Thread Window”);
wnd->ShowWindow(SW_SHOW);
wnd->UpdateWindow();
m_pMainWnd=wnd;
return TRUE;
}
创建新的用户界面线程 void CUIThreadDlg::OnButton1()
{
CUIThread* pThread=new CUIThread();
pThread->CreateThread();
}
请注意以下两点:
A、在UIThreadDlg.cpp的开头加入语句: #include “UIThread.h”
B、把UIThread.h中类CUIThread()的构造函数的特性由 protected 改为 public。
  用户界面线程的执行次序与应用程序主线程相同,首先调用用户界面线程类的InitInstance()函数,如果返回TRUE,继续调用线程的Run()函数,该函数的作用是运行一个标准的消息循环,并且当收到WM_QUIT消息后中断,在消息循环过程中,Run()函数检测到线程空闲时(没有消息),也将调用OnIdle()函数,最后Run()函数返回,MFC调用ExitInstance()函数清理资源。
  你可以创建一个没有界面而有消息循环的线程,例如:你可以从CWinThread派生一个新类,在InitInstance函数中完成某项任务并返回FALSE,这表示仅执行InitInstance函数中的任务而不执行消息循环,你可以通过这种方法,完成一个工作者线程的功能。

例程6 MultiThread6
建立一个基于对话框的工程MultiThread6,在对话框IDD_MULTITHREAD6_DIALOG中加入一个按钮IDC_UI_THREAD,标题为“用户界面线程”
右击工程并选中“New Class…”为工程添加基类为CWinThread派生线程类CUIThread。
给工程添加新对话框IDD_UITHREADDLG,标题为“线程对话框”。为对话框IDD_UITHREADDLG创建一个基于CDialog的类CUIThreadDlg。使用ClassWizard为CUIThreadDlg类添加WM_LBUTTONDOWN消息的处理函数

OnLButtonDown,如下:
void CUIThreadDlg::OnLButtonDown(UINT nFlags, CPoint point)
{
AfxMessageBox(“You Clicked The Left Button!”);
CDialog::OnLButtonDown(nFlags, point);
}
在UIThread.h中添加 #include “UIThreadDlg.h”
并在CUIThread类中添加protected变量CUIThread m_dlg: class CUIThread : public CWinThread
{
DECLARE_DYNCREATE(CUIThread)
protected:
CUIThread(); // protected constructor used by dynamic creation
// Attributes
public:
// Operations
public:
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CUIThread)
public:
virtual BOOL InitInstance();
virtual int ExitInstance();
//}}AFX_VIRTUAL
// Implementation
protected:
CUIThreadDlg m_dlg;
virtual ~CUIThread();
// Generated message map functions
//{{AFX_MSG(CUIThread)
// NOTE – the ClassWizard will add and remove member functions here.
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
分别重载InitInstance()函数和ExitInstance()函数: BOOL CUIThread::InitInstance()
{
m_dlg.Create(IDD_UITHREADDLG);
m_dlg.ShowWindow(SW_SHOW);
m_pMainWnd=&m_dlg;
return TRUE;
}
int CUIThread::ExitInstance()
{
m_dlg.DestroyWindow();
return CWinThread::ExitInstance();
}
双击按钮IDC_UI_THREAD,添加消息响应函数: void CMultiThread6Dlg::OnUiThread()
{
CWinThread *pThread=AfxBeginThread(RUNTIME_CLASS(CUIThread));
}
并在MultiThread6Dlg.cpp的开头添加: #include “UIThread.h”
  好了,编译并运行程序吧。每单击一次“用户界面线程”按钮,都会弹出一个线程对话框,在任何一个线程对话框内按下鼠标左键,都会弹出一个消息框。
七、线程间通讯
  一般而言,应用程序中的一个次要线程总是为主线程执行特定的任务,这样,主线程和次要线程间必定有一个信息传递的渠道,也就是主线程和次要线程间要进行通信。这种线程间的通信不但是难以避免的,而且在多线程编程中也是复杂和频繁的,下面将进行说明。

使用全局变量进行通信
由于属于同一个进程的各个线程共享操作系统分配该进程的资源,故解决线程间通信最简单的一种方法是使用全局变量。对于标准类型的全局变量,我们建议使用volatile
修饰符,它告诉编译器无需对该变量作任何的优化,即无需将它放到一个寄存器中,并且该值可被外部改变。如果线程间所需传递的信息较复杂,我们可以定义一个结构,通过传递指向该结构的指针进行传递信息。
 
使用自定义消息
我们可以在一个线程的执行函数中向另一个线程发送自定义的消息来达到通信的目的。一个线程向另外一个线程发送消息是通过操作系统实现的。利用Windows操作系统的消息驱动机制,当一个线程发出一条消息时,操作系统首先接收到该消息,然后把该消息转发给目标线程,接收消息的线程必须已经建立了消息循环。

例程7 MultiThread7
  该例程演示了如何使用自定义消息进行线程间通信。首先,主线程向CCalculateThread线程发送消息WM_CALCULATE,CCalculateThread线程收到消息后进行计算,再向主线程发送WM_DISPLAY消息,主线程收到该消息后显示计算结果。

建立一个基于对话框的工程MultiThread7,在对话框IDD_MULTITHREAD7_DIALOG中加入三个单选按钮IDC_RADIO1,IDC_RADIO2,IDC_RADIO3,标题分别为1+2+3+4+……+10,1+2+3+4+……+50,1+2+3+4+……+100。加入按钮IDC_SUM,标题为“求和”。加入标签框IDC_STATUS,属性选中“边框”;
在MultiThread7Dlg.h中定义如下变量:
protected:
int nAddend;
代表加数的大小。
分别双击三个单选按钮,添加消息响应函数:
void CMultiThread7Dlg::OnRadio1()
{
nAddend=10;
}
void CMultiThread7Dlg::OnRadio2()
{
nAddend=50;
}
void CMultiThread7Dlg::OnRadio3()
{
nAddend=100;
}
并在OnInitDialog函数中完成相应的初始化工作:
BOOL CMultiThread7Dlg::OnInitDialog()
{
……
((CButton*)GetDlgItem(IDC_RADIO1))->SetCheck(TRUE);
nAddend=10;
……
在MultiThread7Dlg.h中添加:
#include “CalculateThread.h”
#define WM_DISPLAY WM_USER+2
class CMultiThread7Dlg : public CDialog
{
// Construction
public:
CMultiThread7Dlg(CWnd* pParent = NULL); // standard constructor
CCalculateThread* m_pCalculateThread;
……
protected:
int nAddend;
LRESULT OnDisplay(WPARAM wParam,LPARAM lParam);
……
在MultiThread7Dlg.cpp中添加:
BEGIN_MESSAGE_MAP(CMultiThread7Dlg, CDialog)
……
ON_MESSAGE(WM_DISPLAY,OnDisplay)
END_MESSAGE_MAP()
LRESULT CMultiThread7Dlg::OnDisplay(WPARAM wParam,LPARAM lParam)
{
int nTemp=(int)wParam;
SetDlgItemInt(IDC_STATUS,nTemp,FALSE);
return 0;
}
以上代码使得主线程类CMultiThread7Dlg可以处理WM_DISPLAY消息,即在IDC_STATUS标签框中显示计算结果。
双击按钮IDC_SUM,添加消息响应函数:
void CMultiThread7Dlg::OnSum()
{
m_pCalculateThread=
(CCalculateThread*)AfxBeginThread(RUNTIME_CLASS(CCalculateThread));
Sleep(500);
m_pCalculateThread->PostThreadMessage(WM_CALCULATE,nAddend,NULL);
}
OnSum()函数的作用是建立CalculateThread线程,延时给该线程发送WM_CALCULATE消息。
右击工程并选中“New Class…”为工程添加基类为 CWinThread 派生线程类 CCalculateThread。
在文件CalculateThread.h 中添加
#define WM_CALCULATE WM_USER+1
class CCalculateThread : public CWinThread
{
……
protected:
afx_msg LONG OnCalculate(UINT wParam,LONG lParam);
……
在文件CalculateThread.cpp中添加
LONG CCalculateThread::OnCalculate(UINT wParam,LONG
lParam)
{
int nTmpt=0;
for(int i=0;i<=(int)wParam;i++) { nTmpt=nTmpt+i; } Sleep(500); ::PostMessage((HWND)(GetMainWnd()->GetSafeHwnd()),WM_DISPLAY,nTmpt,NULL);
return 0;
}
BEGIN_MESSAGE_MAP(CCalculateThread, CWinThread)
//{{AFX_MSG_MAP(CCalculateThread)
// NOTE – the ClassWizard will add and remove mapping macros here.
//}}AFX_MSG_MAP
ON_THREAD_MESSAGE(WM_CALCULATE,OnCalculate)
//和主线程对比,注意它们的区别
END_MESSAGE_MAP()
在CalculateThread.cpp文件的开头添加一条: #include “MultiThread7Dlg.h”
  以上代码为 CCalculateThread 类添加了 WM_CALCULATE 消息,消息的响应函数是 OnCalculate,其功能是根据参数
wParam 的值,进行累加,累加结果在临时变量nTmpt中,延时0.5秒,向主线程发送WM_DISPLAY消息进行显示,nTmpt作为参数传递。
编译并运行该例程,体会如何在线程间传递消息。
八、线程的同步
  虽然多线程能给我们带来好处,但是也有不少问题需要解决。例如,对于像磁盘驱动器这样独占性系统资源,由于线程可以执行进程的任何代码段,且线程的运行是由系统调度自动完成的,具有一定的不确定性,因此就有可能出现两个线程同时对磁盘驱动器进行操作,从而出现操作错误;又例如,对于银行系统的计算机来说,可能使用一个线程来更新其用户数据库,而用另外一个线程来读取数据库以响应储户的需要,极有可能读数据库的线程读取的是未完全更新的数据库,因为可能在读的时候只有一部分数据被更新过。
  使隶属于同一进程的各线程协调一致地工作称为线程的同步。MFC提供了多种同步对象,下面我们只介绍最常用的四种:
临界区(CCriticalSection)
事件(CEvent)
互斥量(CMutex)
信号量(CSemaphore)
 
通过这些类,我们可以比较容易地做到线程同步。
A、使用 CCriticalSection 类
  当多个线程访问一个独占性共享资源时,可以使用“临界区”对象。任一时刻只有一个线程可以拥有临界区对象,拥有临界区的线程可以访问被保护起来的资源或代码段,其他希望进入临界区的线程将被挂起等待,直到拥有临界区的线程放弃临界区时为止,这样就保证了不会在同一时刻出现多个线程访问共享资源。
CCriticalSection类的用法非常简单,步骤如下:
 
定义CCriticalSection类的一个全局对象(以使各个线程均能访问),如CCriticalSection critical_section;
在访问需要保护的资源或代码之前,调用CCriticalSection类的成员Lock()获得临界区对象: critical_section.Lock();
在线程中调用该函数来使线程获得它所请求的临界区。如果此时没有其它线程占有临界区对象,则调用Lock()的线程获得临界区;否则,线程将被挂起,并放入到一个系统队列中等待,直到当前拥有临界区的线程释放了临界区时为止。
访问临界区完毕后,使用CCriticalSection的成员函数Unlock()来释放临界区:critical_section.Unlock();再通俗一点讲,就是线程A执行到critical_section.Lock();语句时,如果其它线程(B)正在执行critical_section.Lock();语句后且critical_section.Unlock();语句前的语句时,线程A就会等待,直到线程B执行完critical_section. Unlock();语句,线程A才会继续执行。
下面再通过一个实例进行演示说明。
例程8 MultiThread8
建立一个基于对话框的工程MultiThread8,在对话框IDD_MULTITHREAD8_DIALOG中加入两个按钮和两个编辑框控件,两个按钮的ID分别为IDC_WRITEW和IDC_WRITED,标题分别为“写‘W’”和“写‘D’”;两个编辑框的ID分别为IDC_W和IDC_D,属性都选中Read-only;
在MultiThread8Dlg.h文件中声明两个线程函数: UINT WriteW(LPVOID pParam);UINT WriteD(LPVOID pParam);
使用ClassWizard分别给IDC_W和IDC_D添加CEdit类变量m_ctrlW和m_ctrlD;
在MultiThread8Dlg.cpp文件中添加如下内容:
为了文件中能够正确使用同步类,在文件开头添加:#include “afxmt.h”
定义临界区和一个字符数组,为了能够在不同线程间使用,定义为全局变量:
CCriticalSection critical_section;
char g_Array[10];
添加线程函数:UINT WriteW(LPVOID pParam)
{
CEdit *pEdit=(CEdit*)pParam;
pEdit->SetWindowText(“”);
critical_section.Lock();
//锁定临界区,其它线程遇到critical_section.Lock();语句时要等待
//直至执行critical_section.Unlock();语句
for(int i=0;i<10;i++) { g_Array[i]=''W''; pEdit->SetWindowText(g_Array);
Sleep(1000);
}
critical_section.Unlock();
return 0;
}
UINT WriteD(LPVOID pParam)
{
CEdit *pEdit=(CEdit*)pParam;
pEdit->SetWindowText(“”);
critical_section.Lock();
//锁定临界区,其它线程遇到critical_section.Lock();语句时要等待
//直至执行critical_section.Unlock();语句
for(int i=0;i<10;i++) { g_Array[i]=''D''; pEdit->SetWindowText(g_Array);
Sleep(1000);
}
critical_section.Unlock();
return 0;
}
分别双击按钮IDC_WRITEW和IDC_WRITED,添加其响应函数: void CMultiThread8Dlg::OnWritew()
{
CWinThread *pWriteW=AfxBeginThread(WriteW,
&m_ctrlW,
THREAD_PRIORITY_NORMAL,
0,
CREATE_SUSPENDED);
pWriteW->ResumeThread();
}
void CMultiThread8Dlg::OnWrited()
{
CWinThread *pWriteD=AfxBeginThread(WriteD,
&m_ctrlD,
THREAD_PRIORITY_NORMAL,
0,
CREATE_SUSPENDED);
pWriteD->ResumeThread();

}
由于代码较简单,不再详述。编译、运行该例程,您可以连续点击两个按钮,观察体会临界类的作用。
B、使用 CEvent 类
  CEvent类提供了对事件的支持。事件是一个允许一个线程在某种情况发生时,唤醒另外一个线程的同步对象。例如在某些网络应用程序中,一个线程(记为A)负责监听通讯端口,另外一个线程(记为B)负责更新用户数据。通过使用CEvent类,线程A可以通知线程B何时更新用户数据。每一个CEvent 对象可以有两种状态:有信号状态和无信号状态。线程监视位于其中的CEvent类对象的状态,并在相应的时候采取相应的操作。
  在MFC中,CEvent 类对象有两种类型:人工事件和自动事件。一个自动CEvent对象在被至少一个线程释放后会自动返回到无信号状态;而人工事件对象获得信号后,释放可利用线程,但直到调用成员函数ReSetEvent()才将其设置为无信号状态。在创建CEvent类的对象时,默认创建的是自动事件。 CEvent 类的各成员函数的原型和参数说明如下:
1、CEvent(BOOL bInitiallyOwn=FALSE,
BOOL bManualReset=FALSE,
LPCTSTR lpszName=NULL,
LPSECURITY_ATTRIBUTES lpsaAttribute=NULL);
bInitiallyOwn:指定事件对象初始化状态,TRUE为有信号,FALSE为无信号;
bManualReset:指定要创建的事件是属于人工事件还是自动事件。TRUE为人工事件,FALSE为自动事件;
后两个参数一般设为NULL,在此不作过多说明。
2、BOOL CEvent::SetEvent();
  将 CEvent 类对象的状态设置为有信号状态。如果事件是人工事件,则 CEvent 类对象保持为有信号状态,直到调用成员函数ResetEvent()将其重新设为无信号状态时为止。如果CEvent 类对象为自动事件,则在SetEvent()将事件设置为有信号状态后,CEvent类对象由系统自动重置为无信号状态。
如果该函数执行成功,则返回非零值,否则返回零。
3、BOOL CEvent::ResetEvent();
  该函数将事件的状态设置为无信号状态,并保持该状态直至SetEvent()被调用时为止。由于自动事件是由系统自动重置,故自动事件不需要调用该函数。如果该函数执行成功,返回非零值,否则返回零。我们一般通过调用WaitForSingleObject函数来监视事件状态。前面我们已经
介绍了该函数。由于语言描述的原因,CEvent类的理解确实有些难度,但您只要通过仔细回味下面例程,多看几遍就可理解。
例程9 MultiThread9
建立一个基于对话框的工程MultiThread9,在对话框IDD_MULTITHREAD9_DIALOG中加入一个按钮和两个编辑框控件,按钮的ID为IDC_WRITEW,标题为“写‘W’”;两个编辑框的ID分别为IDC_W和IDC_D,属性都选中Read-only;
在MultiThread9Dlg.h文件中声明两个线程函数:
UINT WriteW(LPVOID pParam);
UINT WriteD(LPVOID pParam);
使用ClassWizard分别给IDC_W和IDC_D添加CEdit类变量m_ctrlW和m_ctrlD;
在MultiThread9Dlg.cpp文件中添加如下内容:
为了文件中能够正确使用同步类,在文件开头添加
#include “afxmt.h”
定义事件对象和一个字符数组,为了能够在不同线程间使用,定义为全局变量。 CEvent eventWriteD;
char g_Array[10];
添加线程函数: UINT WriteW(LPVOID pParam)
{
CEdit *pEdit=(CEdit*)pParam;
pEdit->SetWindowText(“”);
for(int i=0;i<10;i++) { g_Array[i]=''W''; pEdit->SetWindowText(g_Array);
Sleep(1000);
}
eventWriteD.SetEvent();
return 0;
}
UINT WriteD(LPVOID pParam)
{
CEdit *pEdit=(CEdit*)pParam;
pEdit->SetWindowText(“”);
WaitForSingleObject(eventWriteD.m_hObject,INFINITE);
for(int i=0;i<10;i++) { g_Array[i]=''D''; pEdit->SetWindowText(g_Array);
Sleep(1000);
}
return 0;
}
  仔细分析这两个线程函数, 您就会正确理解CEvent 类。线程WriteD执行到
WaitForSingleObject(eventWriteD.m_hObject,INFINITE);处等待,直到事件eventWriteD为有信号该线程才往下执行,因为eventWriteD对象是自动事件,则当WaitForSingleObject()返回时,系统自动把eventWriteD对象重置为无信号状态。

双击按钮IDC_WRITEW,添加其响应函数: void CMultiThread9Dlg::OnWritew()
{
CWinThread *pWriteW=AfxBeginThread(WriteW,
&m_ctrlW,
THREAD_PRIORITY_NORMAL,
0,
CREATE_SUSPENDED);
pWriteW->ResumeThread();
CWinThread *pWriteD=AfxBeginThread(WriteD,
&m_ctrlD,
THREAD_PRIORITY_NORMAL,
0,
CREATE_SUSPENDED);
pWriteD->ResumeThread();

}
编译并运行程序,单击“写‘W’”按钮,体会事件对象的作用。
C、使用CMutex 类
  互斥对象与临界区对象很像.互斥对象与临界区对象的不同在于:互斥对象可以在进程间使用,而临界区对象只能在同一进程的各线程间使用。当然,互斥对象也可以用于同一进程的各个线程间,但是在这种情况下,使用临界区会更节省系统资源,更有效率。
D、使用CSemaphore 类
  当需要一个计数器来限制可以使用某个线程的数目时,可以使用“信号量”对象。CSemaphore类的对象保存了对当前访问某一指定资源的线程的计数值,该计数值是当前还可以使用该资源的线程的数目。如果这个计数达到了零,则所有对这个CSemaphore类对象所控制的资源的访问尝试都被放入到一个队列中等待,直到超时或计数值不为零时为止。一个线程被释放已访问了被保护的资源时,计数值减1;一个线程完成了对被控共享资源的访问时,计数值增1。这个被CSemaphore
类对象所控制的资源可以同时接受访问的最大线程数在该对象的构建函数中指定。
CSemaphore 类的构造函数原型及参数说明如下:
CSemaphore (LONG lInitialCount=1,
LONG lMaxCount=1,
LPCTSTR pstrName=NULL,
LPSECURITY_ATTRIBUTES lpsaAttributes=NULL);
lInitialCount:信号量对象的初始计数值,即可访问线程数目的初始值;
lMaxCount:信号量对象计数值的最大值,该参数决定了同一时刻可访问由信号量保护的资源的线程最大数目;
后两个参数在同一进程中使用一般为NULL,不作过多讨论;
  在用CSemaphore类的构造函数创建信号量对象时要同时指出允许的最大资源计数和当前可用资源计数。一般是将当前可用资源计数设置为最大资源计数,每增加一个线程对共享资源的访问,当前可用资源计数就会减1,只要当前可用资源计数是大于0的,就可以发出信号量信号。但是当前可用计数减小到0时,则说明当前占用资源的线程数已经达到了所允许的最大数目,不能再允许其它线程的进入,此时的信号量信号将无法发出。线程在处理完共享资源后,应在离开的同时通过ReleaseSemaphore()函数将当前可用资源数加1。
下面给出一个简单实例来说明 CSemaphore 类的用法。
例程10 MultiThread10
建立一个基于对话框的工程MultiThread10,在对话框IDD_MULTITHREAD10_DIALOG中加入一个按钮和三个编辑框控件,按钮的ID为IDC_START,标题为“同时写‘A’、‘B’、‘C’”;三个编辑框的ID分别为IDC_A、IDC_B和IDC_C,属性都选中Read-only;
在MultiThread10Dlg.h文件中声明两个线程函数: UINT WriteA(LPVOID pParam);
UINT WriteB(LPVOID pParam);
UINT WriteC(LPVOID pParam);
使用ClassWizard分别给IDC_A、IDC_B和IDC_C添加CEdit类变量m_ctrlA、m_ctrlB和m_ctrlC;
在MultiThread10Dlg.cpp文件中添加如下内容:
为了文件中能够正确使用同步类,在文件开头添加:
#include “afxmt.h”
定义信号量对象和一个字符数组,为了能够在不同线程间使用,定义为全局变量:CSemaphore semaphoreWrite(2,2);
//资源最多访问线程2个,当前可访问线程数2个
char g_Array[10];
添加三个线程函数:
UINT WriteA(LPVOID pParam)
{
CEdit *pEdit=(CEdit*)pParam;
pEdit->SetWindowText(“”);
WaitForSingleObject(semaphoreWrite.m_hObject,INFINITE);
CString str;
for(int i=0;i<10;i++) { pEdit->GetWindowText(str);
g_Array[i]=”A”;
str=str+g_Array[i];
pEdit->SetWindowText(str);
Sleep(1000);
}
ReleaseSemaphore(semaphoreWrite.m_hObject,1,NULL);
return 0;
}
UINT WriteB(LPVOID pParam)
{
CEdit *pEdit=(CEdit*)pParam;
pEdit->SetWindowText(“”);
WaitForSingleObject(semaphoreWrite.m_hObject,INFINITE);
CString str;
for(int i=0;i<10;i++) { pEdit->GetWindowText(str);
g_Array[i]=”B”;
str=str+g_Array[i];
pEdit->SetWindowText(str);
Sleep(1000);
}
ReleaseSemaphore(semaphoreWrite.m_hObject,1,NULL);
return 0;
}
UINT WriteC(LPVOID pParam)
{
CEdit *pEdit=(CEdit*)pParam;
pEdit->SetWindowText(“”);
WaitForSingleObject(semaphoreWrite.m_hObject,INFINITE);
for(int i=0;i<10;i++) { g_Array[i]=''C''; pEdit->SetWindowText(g_Array);
Sleep(1000);
}
ReleaseSemaphore(semaphoreWrite.m_hObject,1,NULL);
return 0;
}
这三个线程函数不再多说。在信号量对象有信号的状态下,线程执行到WaitForSingleObject语句处继续执行,同时可用线程数减1;若线程执行到WaitForSingleObject语句时信号量对象无信号,线程就在这里等待,直到信号量对象有信号线程才往下执行。
双击按钮IDC_START,添加其响应函数: void CMultiThread10Dlg::OnStart()
{
CWinThread *pWriteA=AfxBeginThread(WriteA,
&m_ctrlA,
THREAD_PRIORITY_NORMAL,
0,
CREATE_SUSPENDED);
pWriteA->ResumeThread();
CWinThread *pWriteB=AfxBeginThread(WriteB,
&m_ctrlB,
THREAD_PRIORITY_NORMAL,
0,
CREATE_SUSPENDED);
pWriteB->ResumeThread();
CWinThread *pWriteC=AfxBeginThread(WriteC,
&m_ctrlC,
THREAD_PRIORITY_NORMAL,
0,
CREATE_SUSPENDED);
pWriteC->ResumeThread();

}
好吧,多线程编程就介绍到这里,希望本文能对您有所帮助。

详解C中volatile关键字

volatile提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据。如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象。下面举例说明。在DSP开发中,经常需要等待某个事件的触发,所以经常会写出这样的程序:

这段程序等待内存变量flag的值变为1(怀疑此处是0,有点疑问,)之后才运行do2()。变量flag的值由别的程序更改,这个程序可能是某个硬件中断服务程序。例如:如果某个按钮按下的话,就会对DSP产生中断,在按键中断程序中修改flag为1,这样上面的程序就能够得以继续运行。但是,编译器并不知道flag的值会被别的程序修改,因此在它进行优化的时候,可能会把flag的值先读入某个寄存器,然后等待那个寄存器变为1。如果不幸进行了这样的优化,那么while循环就变成了死循环,因为寄存器的内容不可能被中断服务程序修改。为了让程序每次都读取真正flag变量的值,就需要定义为如下形式:
volatile short flag;
需要注意的是,没有volatile也可能能正常运行,但是可能修改了编译器的优化级别之后就又不能正常运行了。因此经常会出现debug版本正常,但是release版本却不能正常的问题。所以为了安全起见,只要是等待别的程序修改某个变量的话,就加上volatile关键字。

volatile的本意是“易变的”
由于访问寄存器的速度要快过RAM,所以编译器一般都会作减少存取外部RAM的优化。比如:

程序的本意是希望ISR_2中断产生时,在main当中调用do_something函数,但是,由于编译器判断在main函数里面没有修改过i,因此可能只执行一次对从i到某寄存器的读操作,然后每次if判断都只使用这个寄存器里面的“i副本”,导致do_something永远也不会被调用。如果变量加上volatile修饰,则编译器保证对此变量的读写操作都不会被优化(肯定执行)。此例中i也应该如此说明。
一般说来,volatile用在如下的几个地方:
1、中断服务程序中修改的供其它程序检测的变量需要加volatile;
2、多任务环境下各任务间共享的标志应该加volatile;
3、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;
另外,以上这几种情况经常还要同时考虑数据的完整性(相互关联的几个标志读了一半被打断了重写),在1中可以通过关中断来实现,2中可以禁止任务调度,3中则只能依靠硬件的良好设计了。
二、volatile 的含义
volatile总是与优化有关,编译器有一种技术叫做数据流分析,分析程序中的变量在哪里赋值、在哪里使用、在哪里失效,分析结果可以用于常量合并,常量传播等优化,进一步可以死代码消除。但有时这些优化不是程序所需要的,这时可以用volatile关键字禁止做这些优化,volatile的字面含义是易变的,它有下面的作用:
1 不会在两个操作之间把volatile变量缓存在寄存器中。在多任务、中断、甚至setjmp环境下,变量可能被其他的程序改变,编译器自己无法知道,volatile就是告诉编译器这种情况。
2 不做常量合并、常量传播等优化,所以像下面的代码:
volatile int i = 1;
if (i > 0) …
if的条件不会当作无条件真。
3 对volatile变量的读写不会被优化掉。如果你对一个变量赋值但后面没用到,编译器常常可以省略那个赋值操作,然而对Memory Mapped IO的处理是不能这样优化的。
前面有人说volatile可以保证对内存操作的原子性,这种说法不大准确,其一,x86需要LOCK前缀才能在SMP下保证原子性,其二,RISC根本不能对内存直接运算,要保证原子性得用别的方法,如atomic_inc。
对于jiffies,它已经声明为volatile变量,我认为直接用jiffies++就可以了,没必要用那种复杂的形式,因为那样也不能保证原子性。
你可能不知道在Pentium及后续CPU中,下面两组指令
inc jiffies
;;
mov jiffies, %eax
inc %eax
mov %eax, jiffies
作用相同,但一条指令反而不如三条指令快。
三、编译器优化 → C关键字volatile → memory破坏描述符zz

“memory”比较特殊,可能是内嵌汇编中最难懂部分。为解释清楚它,先介绍一下编译器的优化知识,再看C关键字volatile。最后去看该描述符。
1、编译器优化介绍
内存访问速度远不及CPU处理速度,为提高机器整体性能,在硬件上引入硬件高速缓存Cache,加速对内存的访问。另外在现代CPU中指令的执行并不一定严格按照顺序执行,没有相关性的指令可以乱序执行,以充分利用CPU的指令流水线,提高执行速度。以上是硬件级别的优化。再看软件一级的优化:一种是在编写代码时由程序员优化,另一种是由编译器进行优化。编译器优化常用的方法有:将内存变量缓存到寄存器;调整指令顺序充分利用CPU指令流水线,常见的是重新排序读写指令。对常规内存进行优化的时候,这些优化是透明的,而且效率很好。由编译器优化或者硬件重新排序引起的问题的解决办法是在从硬件(或者其他处理器)的角度看必须以特定顺序执行的操作之间设置内存屏障(memory barrier),linux 提供了一个宏解决编译器的执行顺序问题。
void Barrier(void)
这个函数通知编译器插入一个内存屏障,但对硬件无效,编译后的代码会把当前CPU寄存器中的所有修改过的数值存入内存,需要这些数据的时候再重新从内存中读出。
2、C语言关键字volatile
C语言关键字volatile(注意它是用来修饰变量而不是上面介绍的__volatile__)表明某个变量的值可能在外部被改变,因此对这些变量的存取不能缓存到寄存器,每次使用时需要重新存取。该关键字在多线程环境下经常使用,因为在编写多线程的程序时,同一个变量可能被多个线程修改,而程序通过该变量同步各个线程,例如:

该线程启动时将intSignal 置为2,然后循环等待直到intSignal 为1 时退出。显然intSignal的值必须在外部被改变,否则该线程不会退出。但是实际运行的时候该线程却不会退出,即使在外部将它的值改为1,看一下对应的伪汇编代码就明白了:
mov ax,signal
label:
if(ax!=1)
goto label
对于C编译器来说,它并不知道这个值会被其他线程修改。自然就把它cache在寄存器里面。记住,C 编译器是没有线程概念的!这时候就需要用到volatile。volatile 的本意是指:这个值可能会在当前线程外部被改变。也就是说,我们要在threadFunc中的intSignal前面加上volatile关键字,这时候,编译器知道该变量的值会在外部改变,因此每次访问该变量时会重新读取,所作的循环变为如下面伪码所示:
label:
mov ax,signal
if(ax!=1)
goto label
3、Memory
有了上面的知识就不难理解Memory修改描述符了,Memory描述符告知GCC:
1)不要将该段内嵌汇编指令与前面的指令重新排序;也就是在执行内嵌汇编代码之前,它前面的指令都执行完毕
2)不要将变量缓存到寄存器,因为这段代码可能会用到内存变量,而这些内存变量会以不可预知的方式发生改变,因此GCC插入必要的代码先将缓存到寄存器的变量值写回内存,如果后面又访问这些变量,需要重新访问内存。
如果汇编指令修改了内存,但是GCC 本身却察觉不到,因为在输出部分没有描述,此时就需要在修改描述部分增加“memory”,告诉GCC 内存已经被修改,GCC 得知这个信息后,就会在这段指令之前,插入必要的指令将前面因为优化Cache 到寄存器中的变量值先写回内存,如果以后又要使用这些变量再重新读取。
使用“volatile”也可以达到这个目的,但是我们在每个变量前增加该关键字,不如使用“memory”方便。

常数静态成员初始化

利用SVN实现自动版本号生成

SVN是当前比较流行的版本控制软件,下面主要介绍通过SVN在程序编译时自动生成版本号。
基本思想:利用SubWCRev.exe获取版本信息,读入屏幕流,生成包括版本号信息的“.H文件”,并修改“.rc资源文件”中的版本信息,“.H文件”主要用于关于框中的版本显示,简单格式如下:
#ifndef __SVN_VERSION_MAX
#define __SVN_VERSION_MAX XXX
#endif
用下面附录的源码编译SVNVersionGen.exe

批处理使用说明:

SVNVersionGen源代码

SVNVersionGen.rar下载

c语言读写ini

一共四个文件:inifile.c,inifile.h,main.c.程序中用到的配置文件myconfig.ini不需要提前存在,代码中有创建的过程。
inifile.h是inifile.c的头文件完成读写配置文件的功能,main.c调用inifile完成的功能。

C++著名程序库的比较和学习经验

1、C++各大有名库的介绍——C++标准库
2、C++各大有名库的介绍——准标准库Boost
3、C++各大有名库的介绍——GUI
4、C++各大有名库的介绍——网络通信
5、C++各大有名库的介绍——XML
6、C++各大有名库的介绍——科学计算
7、C++各大有名库的介绍——游戏开发
8、C++各大有名库的介绍——线程
9、C++各大有名库的介绍——序列化
10、C++各大有名库的介绍——字符串
11、C++各大有名库的介绍——综合
12、C++各大有名库的介绍——其他库
13、C++名人的网站

在 C++中,库的地位是非常高的。C++之父 Bjarne Stroustrup先生多次表示了设计库来扩充功能要好过设计更多的语法的言论。现实中,C++的库门类繁多,解决的问题也是极其广泛,库从轻量级到重 量级的都有。不少都是让人眼界大开,亦或是望而生叹的思维杰作。由于库的数量非常庞大,而且限于笔者水平,其中很多并不了解。所以文中所提的一些库都是比 较著名的大型库。

1、C++各大有名库的介绍——C++标准库

标准库中提供了C++程序的基本设施。虽然C++标准库随着C++标准折腾了许多年,直到标准的出台才正式定型,但是在标准库的实现上却很令人欣慰得看到多种实现,并且已被实践证明为有工业级别强度的佳作。
1.1、Dinkumware C++ Library

参考站点:http://www.dinkumware.com/

P.J. Plauger编写的高品质的标准库。P.J. Plauger博士是Dr. Dobb’s程序设计杰出奖的获得者。其编写的库长期被Microsoft采用,并且最近Borland也取得了其OEM的license,在其 C/C++的产品中采用Dinkumware的库。

1.2、RogueWave Standard C++ Library

参考站点:http://www.roguewave.com/

这个库在Borland C++ Builder的早期版本中曾经被采用,后来被其他的库给替换了。笔者不推荐使用。

1.3、SGI STL

参考站点:http://www.roguewave.com/

SGI公司的C++标准模版库。

1.4、STLport

参考站点:http://www.stlport.org/

SGI STL库的跨平台可移植版本。

2、C++各大有名库的介绍——准标准库Boost

Boost库是一个经过千锤百炼、可移植、提供源代码的C++库,作为标准库的后备,是C++标准化进程的发动机之一。 Boost库由C++标准委员会库工作组成员发起,在C++社区中影响甚大,其成员已近2000人。 Boost库为我们带来了最新、最酷、最实用的技术,是不折不扣的“准”标准库。Boost中比较有名气的有这么几个库:

2.1 Regex  正则表达式库

2.2 Spirit   LL parser framework,用C++代码直接表达EBNF

2.3 Graph  图组件和算法

2.4 Lambda  在调用的地方定义短小匿名的函数对象,很实用的functional功能

2.5 concept check   检查泛型编程中的concept

2.6 Mpl   用模板实现的元编程框架

2.7 Thread   可移植的C++多线程库

2.8 Python   把C++类和函数映射到Python之中

2.9 Pool    内存池管理

2.10 smart_ptr   5个智能指针,学习智能指针必读,一份不错的参考是来自CUJ的文章:

Smart Pointers in Boost,哦,这篇文章可以查到,CUJ是提供在线浏览的。中文版见笔者在《Dr.Dobb’s Journal软件研发杂志》第7辑上的译文。

Boost总体来说是实用价值很高,质量很高的库。并且由于其对跨平台的强调,对标准C++的强调,是编写平台无关,现代C++的开发者必备的 工具。但是Boost中也有很多是实验性质的东西,在实际的开发中实用需要谨慎。并且很多Boost中的库功能堪称对语言功能的扩展,其构造用尽精巧的手 法,不要贸然的花费时间研读。Boost另外一面,比如Graph这样的库则是具有工业强度,结构良好,非常值得研读的精品代码,并且也可以放心的在产品 代码中多多利用。

参考站点:http://www.boost.org

3、C++各大有名库的介绍——GUI

在众多C++的库中,GUI部分的库算是比较繁荣,也比较引人注目的。在实际开发中,GUI库的选择也是非常重要的一件事情,下面我们综述一下可选择的GUI库,各自的特点以及相关工具的支持。3.1、MFC

大名鼎鼎的微软基础类库(Microsoft Foundation Class)。大凡学过VC++的人都应该知道这个库。虽然从技术角度讲,MFC是不大漂亮的,但是它构建于Windows API 之上,能够使程序员的工作更容易,编程效率高,减少了大量在建立 Windows 程序时必须编写的代码,同时它还提供了所有一般 C++ 编程的优点,例如继承和封装。MFC 编写的程序在各个版本的Windows操作系统上是可移植的,例如,在Windows 3.1下编写的代码可以很容易地移植到 Windows NT 或 Windows 95 上。但是在最近发展以及官方支持上日渐势微。

3.2、QT

参考网站:http://www.trolltech.com

Qt是Trolltech公司的一个多平台的C++图形用户界面应用程序框架。它提供给应用程序开发者建立艺术级的图形用户界面所需的所用功 能。Qt是完全面向对象的很容易扩展,并且允许真正地组件编程。自从1996年早些时候,Qt进入商业领域,它已经成为全世界范围内数千种成功的应用程序 的基础。Qt也是流行的Linux桌面环境KDE 的基础,同时它还支持Windows、Macintosh、Unix/X11等多种平台。[wangxinus注:QT目前已经是Nokia旗下的产品,原官方网站已经失效,目前为http://qt.nokia.com.2009年初发布的Qt4.5版本开始使用LGPL协议,诺基亚希望以此来吸引更多的开发人员使用Qt库]

3.3、WxWindows

参考网站:http://www.wxwindows.org

跨平台的GUI库。因为其类层次极像MFC,所以有文章介绍从MFC到WxWindows的代码移植以实现跨平台的功能。通过多年的开发也是一个日趋完善的GUI库,支持同样不弱于前面两个库。并且是完全开放源代码的。新近的C++ Builder X的GUI设计器就是基于这个库的。[wangxinus注:迫于微软的施压,已经由WxWindows更名为wxWidgets]

3.4、Fox

参考网站:http://www.fox-toolkit.org/

开放源代码的GUI库。作者从自己亲身的开发经验中得出了一个理想的GUI库应该是什么样子的感受出发,从而开始了对这个库的开发。有兴趣的可以尝试一下。

3.5、WTL

基于ATL的一个库。因为使用了大量ATL的轻量级手法,模板等技术,在代码尺寸,以及速度优化方面做得非常到位。主要面向的使用群体是开发COM轻量级供网络下载的可视化控件的开发者。

3.6、GTK

参考网站:http://gtkmm.sourceforge.net/

GTK是一个大名鼎鼎的C的开源GUI库。在Linux世界中有Gnome这样的杀手应用。而Qt就是这个库的C++封装版本。[wangxinus注:“Qt 就是这个库的C++封装版本”是错误的。Qt早于GTK,最初Qt由于协议的原因引起社区的不满,另外开发了一个基于C语言的GTK库,后面的扩展版本为 GTK+。GTK+的Gnome和Qt的KDE是目前linux桌面的两大阵营,曾有水火不容之势。目前双方都以及开源社区的精神,已经和解。]

4、C++各大有名库的介绍——网络通信

4.1、ACE

参考网站:http://www.cs.wustl.edu/~schmidt/ACE.html

C++库的代表,超重量级的网络通信开发框架。ACE自适配通信环境(Adaptive Communication Environment)是可以自由使用、开放源代码的面向对象框架,在其中实现了许多用于并发通信软件的核心模式。ACE提供了一组丰富的可复用C++ 包装外观(Wrapper Facade)和框架组件,可跨越多种平台完成通用的通信软件任务,其中包括:事件多路分离和事件处理器分派、信号处理、服务初始化、进程间通信、共享内存管理、消息路由、分布式服务动态(重)配置、并发执行和同步,等等。

4.2、StreamModule

参考网站:http://www.omnifarious.org/StrMod

设计用于简化编写分布式程序的库。尝试着使得编写处理异步行为的程序更容易,而不是用同步的外壳包起异步的本质。

4.3、SimpleSocket

参考网站:http://home.hetnet.nl/~lcbokkers/simsock.htm

这个类库让编写基于socket的客户/服务器程序更加容易。

4.4、A Stream Socket API for C++

参考网站:http://www.pcs.cnu.edu/~dgame/sockets/socketsC++/sockets.html

又一个对Socket的封装库。

5、C++各大有名库的介绍——XML

5.1、Xerces

参考网站:http://xml.apache.org/xerces-c/

Xerces-C++ 是一个非常健壮的XML解析器,它提供了验证,以及SAX和DOM API。XML验证在文档类型定义(Document Type Definition,DTD)方面有很好的支持,并且在2001年12月增加了支持W3C XMLSchema 的基本完整的开放标准。

5.2、XMLBooster

参考网站:http://www.xmlbooster.com/

这个库通过产生特制的parser的办法极大的提高了XML解析的速度,并且能够产生相应的GUI程序来修改这个parser。在DOM和SAX两大主流XML解析办法之外提供了另外一个可行的解决方案。

5.3、Pull Parser

参考网站:http://www.extreme.indiana.edu/xgws/xsoap/xpp

这个库采用pull方法的parser。在每个SAX的parser底层都有一个pull的parser,这个xpp把这层暴露出来直接给大家使用。在要充分考虑速度的时候值得尝试。

5.4、Xalan

参考网站:http://xml.apache.org/xalan-c/

Xalan是一个用于把XML文档转换为HTML,纯文本或者其他XML类型文档的XSLT处理器。

5.5、CMarkup

参考网站:http://www.firstobject.com/xml.htm

这是一种使用EDOM的XML解析器。在很多思路上面非常灵活实用。值得大家在DOM和SAX之外寻求一点灵感。

5.6、libxml++

http://libxmlplusplus.sourceforge.net/

libxml++是对著名的libxml XML解析器的C++封装版本。

5.7. TinyXML [wangxinus注:一个非常小巧的XML解析库,基于DOM的。]

6、C++各大有名库的介绍——科学计算

6.1、Blitz++

参考网站:http://www.oonumerics.org/blitz

Blitz++ 是一个高效率的数值计算函数库,它的设计目的是希望建立一套既具像C++ 一样方便,同时又比Fortran速度更快的数值计算环境。通常,用C++所写出的数值程序,比 Fortran慢20%左右,因此Blitz++正是要改掉这个缺点。方法是利用C++的template技术,程序执行甚至可以比Fortran更快。

Blitz++目前仍在发展中,对于常见的SVD,FFTs,QMRES等常见的线性代数方法并不提供,不过使用者可以很容易地利用Blitz++所提供的函数来构建。

6.2、POOMA

参考网站:http://www.codesourcery.com/pooma/pooma

POOMA是一个免费的高性能的C++库,用于处理并行式科学计算。POOMA的面向对象设计方便了快速的程序开发,对并行机器进行了优化以达到最高的效率,方便在工业和研究环境中使用。

6.3、MTL

参考网站:http://www.osl.iu.edu/research/mtl

Matrix Template Library(MTL)是一个高性能的泛型组件库,提供了各种格式矩阵的大量线性代数方面的功能。在某些应用使用高性能编译器的情况下,比如Intel的编译器,从产生的汇编代码可以看出其与手写几乎没有两样的效能。

6.4、CGAL

参考网站:www.cgal.org

Computational Geometry Algorithms Library的目的是把在计算几何方面的大部分重要的解决方案和方法以C++库的形式提供给工业和学术界的用户。

7、C++各大有名库的介绍——游戏开发

7.1、Audio/Video 3D C++ Programming Library

参考网站:http://www.galacticasoftware.com/products/av/

AV3D是一个跨平台,高性能的C++库。主要的特性是提供3D图形,声效支持(SB,以及S3M),控制接口(键盘,鼠标和遥感),XMS。

7.2、KlayGE

参考网站:http://home.g365.net/enginedev/

国内游戏开发高手自己用C++开发的游戏引擎。KlayGE是一个开放源代码、跨平台的游戏引擎,并使用Python作脚本语言。KlayGE在LGPL协议下发行。感谢龚敏敏先生为中国游戏开发事业所做出的贡献。

[wangxinus注:这个库国人了解很少,百度百科的KlayGE词条还是本人创建的。一个人开发一个游戏引擎库,是在让笔者汗颜,对作者表示钦佩!]

7.3、OGRE

参考网站:http://www.ogre3d.org

OGRE(面向对象的图形渲染引擎)是用C++开发的,使用灵活的面向对象3D引擎。它的目的是让开发者能更方便和直接地开发基于3D硬件设备 的应用程序或游戏。引擎中的类库对更底层的系统库(如:Direct3D和OpenGL)的全部使用细节进行了抽象,并提供了基于现实世界对象的接口和其 它类。

8、C++各大有名库的介绍——线程

8.1、C++ Threads

参考网站:http://threads.sourceforge.net/

这个库的目标是给程序员提供易于使用的类,这些类被继承以提供在Linux环境中很难看到的大量的线程方面的功能。

8.2、ZThreads

参考网站:http://zthread.sourceforge.net/

一个先进的面向对象,跨平台的C++线程和同步库。

9、C++各大有名库的介绍——序列化

9.1、s11n

参考网站:http://s11n.net/

一个基于STL的C++库,用于序列化POD,STL容器以及用户定义的类型。

9.2、Simple XML Persistence Library

参考网站:http://sxp.sourceforge.net/

这是一个把对象序列化为XML的轻量级的C++库。

10、C++各大有名库的介绍——字符串

10.1、C++ Str Library

参考网站:http://www.utilitycode.com/str/

操作字符串和字符的库,支持Windows和支持gcc的多种平台。提供高度优化的代码,并且支持多线程环境和Unicode,同时还有正则表达式的支持。

10.2、Common Text Transformation Library

参考网站:http://cttl.sourceforge.net/

这是一个解析和修改STL字符串的库。CTTL substring类可以用来比较,插入,替换以及用EBNF的语法进行解析。

10.3、GRETA

参考网站:http://research.microsoft.com/projects/greta/

这是由微软研究院的研究人员开发的处理正则表达式的库。在小型匹配的情况下有非常优秀的表现。

11、C++各大有名库的介绍——综合

11.1、P::Classes

参考网站:http://pclasses.com/

一个高度可移植的C++应用程序框架。当前关注类型和线程安全的signal/slot机制,i/o系统包括基于插件的网络协议透明的i/o架构,基于插件的应用程序消息日志框架,访问sql数据库的类等等。

11.2、ACDK – Artefaktur Component Development Kit

参考网站:http://acdk.sourceforge.net/

这是一个平台无关的C++组件框架,类似于Java或者.NET中的框架(反射机制,线程,Unicode,废料收集,I/O,网络,实用工具,XML,等等),以及对Java, Perl, Python, TCL, Lisp, COM 和 CORBA的集成。

11.3、dlib C++ library

参考网站:http://www.cis.ohio-state.edu/~kingd/dlib/

各种各样的类的一个综合。大整数,Socket,线程,GUI,容器类,以及浏览目录的API等等。

11.4、Chilkat C++ Libraries

参考网站:http://www.chilkatsoft.com/cpp_libraries.asp

这是提供zip,e-mail,编码,S/MIME,XML等方面的库。

11.5、C++ Portable Types Library (PTypes)

参考网站:http://www.melikyan.com/ptypes/

这是STL的比较简单的替代品,以及可移植的多线程和网络库。

11.6、LFC

参考网站:http://lfc.sourceforge.net/

哦,这又是一个尝试提供一切的C++库

12、C++各大有名库的介绍——其他库

12.1、Loki

参考网站:http://www.moderncppdesign.com/

哦,你可能抱怨我早该和Boost一起介绍它,一个实验性质的库。作者在loki中把C++模板的功能发挥到了极致。并且尝试把类似设计模式这样思想层面的东西通过库来提供。同时还提供了智能指针这样比较实用的功能。

12.2、ATL

ATL(Active Template Library)是一组小巧、高效、灵活的类,这些类为创建可互操作的COM组件提供了基本的设施。

12.3、FC++: The Functional C++ Library

这个库提供了一些函数式语言中才有的要素。属于用库来扩充语言的一个代表作。如果想要在OOP之外寻找另一分的乐趣,可以去看看函数式程序设计的世界。大师Peter Norvig在 “Teach Yourself Programming in Ten Years”一文中就将函数式语言列为至少应当学习的6类编程语言之一。

12.4、FACT!

参考网站:http://www.kfa-juelich.de/zam/FACT/start/index.html

另外一个实现函数式语言特性的库

12.5、Crypto++

提供处理密码,消息验证,单向hash,公匙加密系统等功能的免费库。

还有很多非常激动人心或者是极其实用的C++库,限于我们的水平以及文章的篇幅不能包括进来。在对于这些已经包含近来的库的介绍中,由于并不是每一个我们都使用过,所以难免有偏颇之处,请读者见谅。

13、C++名人的网站
正如我们可以通过计算机历史上的重要人物了解计算机史的发展,C++相关人物的网站也可以使我们得到最有价值的参考与借鉴,下面的人物我们认为没 有介绍的必要,只因下面的人物在C++领域的地位众所周知,我们只将相关的资源进行罗列以供读者学习,他们有的工作于贝尔实验室,有的工作于知名编译器厂 商,有的在不断推进语言的标准化,有的为读者撰写了多部千古奇作……

1、Bjarne Stroustrup

http://www.research.att.com/~bs/

2、Stanley B. Lippman

http://blogs.msdn.com/slippman/ (中文版)

http://www.zengyihome.net/slippman/index.htm

3、Scott Meyers

http://www.aristeia.com/

4、David Musser

http://www.cs.rpi.edu/~musser/

5、Bruce Eckel

http://www.bruceeckel.com

http://blog.csdn.net/beckel Bruce Eckel 博客中文版

6、Nicolai M. Josuttis

http://www.josuttis.com/

7、Herb Sutter

http://www.gotw.ca/

http://blog.csdn.net/hsutter/ Herb Sutter 中文博客

8、Andrei Alexandrescu

http://www.moderncppdesign.com