|

信号量
信号量是维护0到指定最大值之间的同步对象。信号量状态在其计数大于0时是有信号的,而其计数是0时是无信号的。信号量对象在控制上可以支持有限数量共享资源的访问。
信号量的特点和用途可用下列几句话定义:
(1)如果当前资源的数量大于0,则信号量有效;
(2)如果当前资源数量是0,则信号量无效;
(3)系统决不允许当前资源的数量为负值;
(4)当前资源数量决不能大于最大资源数量。
创建信号量
|
HANDLE CreateSemaphore (
PSECURITY_ATTRIBUTE psa,
LONG lInitialCount, //开始时可供使用的资源数
LONG lMaximumCount, //最大资源数
PCTSTR pszName);
|
释放信号量
通过调用ReleaseSemaphore函数,线程就能够对信标的当前资源数量进行递增,该函数原型为:
|
BOOL WINAPI ReleaseSemaphore(
HANDLE hSemaphore,
LONG lReleaseCount, //信号量的当前资源数增加lReleaseCount
LPLONG lpPreviousCount
);
|
打开信号量
和其他核心对象一样,信号量也可以通过名字跨进程访问,打开信号量的API为:
|
HANDLE OpenSemaphore (
DWORD fdwAccess,
BOOL bInherithandle,
PCTSTR pszName
);
|
互锁访问
当必须以原子操作方式来修改单个值时,互锁访问函数是相当有用的。所谓原子访问,是指线程在访问资源时能够确保所有其他线程都不在同一时间内访问相同的资源。
请看下列代码:
|
int globalVar = 0;
DWORD WINAPI ThreadFunc1(LPVOID n)
{
globalVar++;
return 0;
}
DWORD WINAPI ThreadFunc2(LPVOID n)
{
globalVar++;
return 0;
}
|
运行ThreadFunc1和ThreadFunc2线程,结果是不可预料的,因为globalVar++并不对应着一条机器指令,我们看看globalVar++的反汇编代码:
|
00401038 mov eax,[globalVar (0042d3f0)]
0040103D add eax,1
00401040 mov [globalVar (0042d3f0)],eax
|
在"mov eax,[globalVar (0042d3f0)]" 指令与"add eax,1" 指令以及"add
eax,1" 指令与"mov [globalVar
(0042d3f0)],eax"指令之间都可能发生线程切换,使得程序的执行后globalVar的结果不能确定。我们可以使用InterlockedExchangeAdd函数解决这个问题:
|
int globalVar = 0;
DWORD WINAPI ThreadFunc1(LPVOID n)
{
InterlockedExchangeAdd(&globalVar,1);
return 0;
}
DWORD WINAPI ThreadFunc2(LPVOID n)
{
InterlockedExchangeAdd(&globalVar,1);
return 0;
}
|
InterlockedExchangeAdd保证对变量globalVar的访问具有"原子性"。互锁访问的控制速度非常快,调用一个互锁函数的CPU周期通常小于50,不需要进行用户方式与内核方式的切换(该切换通常需要运行1000个CPU周期)。
互锁访问函数的缺点在于其只能对单一变量进行原子访问,如果要访问的资源比较复杂,仍要使用临界区或互斥。
可等待定时器
可等待定时器是在某个时间或按规定的间隔时间发出自己的信号通知的内核对象。它们通常用来在某个时间执行某个操作。
创建可等待定时器
|
HANDLE CreateWaitableTimer(
PSECURITY_ATTRISUTES psa,
BOOL fManualReset,//人工重置或自动重置定时器
PCTSTR pszName);
|
设置可等待定时器
可等待定时器对象在非激活状态下被创建,程序员应调用 SetWaitableTimer函数来界定定时器在何时被激活:
|
BOOL SetWaitableTimer(
HANDLE hTimer, //要设置的定时器
const LARGE_INTEGER *pDueTime, //指明定时器第一次激活的时间
LONG lPeriod, //指明此后定时器应该间隔多长时间激活一次
PTIMERAPCROUTINE pfnCompletionRoutine,
PVOID PvArgToCompletionRoutine,
BOOL fResume);
|
取消可等待定时器
|
BOOl Cancel WaitableTimer(
HANDLE hTimer //要取消的定时器
);
|
打开可等待定时器
作为一种内核对象,WaitableTimer也可以被其他进程以名字打开:
|
HANDLE OpenWaitableTimer (
DWORD fdwAccess,
BOOL bInherithandle,
PCTSTR pszName
);
|
实例
下面给出的一个程序可能发生死锁现象:
|
#include
#include
CRITICAL_SECTION cs1, cs2;
long WINAPI ThreadFn(long);
main()
{
long iThreadID;
InitializeCriticalSection(&cs1);
InitializeCriticalSection(&cs2);
CloseHandle(CreateThread(NULL, 0,
(LPTHREAD_START_ROUTINE)ThreadFn, NULL, 0,&iThreadID));
while (TRUE)
{
EnterCriticalSection(&cs1);
printf("\n线程1占用临界区1");
EnterCriticalSection(&cs2);
printf("\n线程1占用临界区2");
printf("\n线程1占用两个临界区");
LeaveCriticalSection(&cs2);
LeaveCriticalSection(&cs1);
printf("\n线程1释放两个临界区");
Sleep(20);
};
return (0);
}
long WINAPI ThreadFn(long lParam)
{
while (TRUE)
{
EnterCriticalSection(&cs2);
printf("\n线程2占用临界区2");
EnterCriticalSection(&cs1);
printf("\n线程2占用临界区1");
printf("\n线程2占用两个临界区");
LeaveCriticalSection(&cs1);
LeaveCriticalSection(&cs2);
printf("\n线程2释放两个临界区");
Sleep(20);
};
}
|
运行这个程序,在中途一旦发生这样的输出:
线程1占用临界区1
线程2占用临界区2
或
线程2占用临界区2
线程1占用临界区1
或
线程1占用临界区2
线程2占用临界区1
或
线程2占用临界区1
线程1占用临界区2
程序就"死"掉了,再也运行不下去。因为这样的输出,意味着两个线程相互等待对方释放临界区,也即出现了死锁。
如果我们将线程2的控制函数改为:
|
long WINAPI ThreadFn(long lParam)
{
while (TRUE)
{
EnterCriticalSection(&cs1);
printf("\n线程2占用临界区1");
EnterCriticalSection(&cs2);
printf("\n线程2占用临界区2");
printf("\n线程2占用两个临界区");
LeaveCriticalSection(&cs1);
LeaveCriticalSection(&cs2);
printf("\n线程2释放两个临界区");
Sleep(20);
};
}
|
再次运行程序,死锁被消除,程序不再挡掉。这是因为我们改变了线程2中获得临界区1、2的顺序,消除了线程1、2相互等待资源的可能性。
由此我们得出结论,在使用线程间的同步机制时,要特别留心死锁的发生。
深入浅出Win32多线程设计之MFC的多线程
1、创建和终止线程
在MFC程序中创建一个线程,宜调用AfxBeginThread函数。该函数因参数不同而具有两种重载版本,分别对应工作者线程和用户接口(UI)线程。
工作者线程
|
CWinThread *AfxBeginThread(
AFX_THREADPROC pfnThreadProc, //控制函数
LPVOID pParam, //传递给控制函数的参数
int nPriority = THREAD_PRIORITY_NORMAL, //线程的优先级
UINT nStackSize = 0, //线程的堆栈大小
DWORD dwCreateFlags = 0, //线程的创建标志
LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL //线程的安全属性
);
|
工作者线程编程较为简单,只需编写线程控制函数和启动线程即可。下面的代码给出了定义一个控制函数和启动它的过程:
|
//线程控制函数
UINT MfcThreadProc(LPVOID lpParam)
{
CExampleClass *lpObject = (CExampleClass*)lpParam;
if (lpObject == NULL ||
!lpObject->IsKindof(RUNTIME_CLASS(CExampleClass)))
return - 1; //输入参数非法
//线程成功启动
while (1)
{
...//
}
return 0;
}
//在MFC程序中启动线程
AfxBeginThread(MfcThreadProc, lpObject);
|
UI线程
创建用户界面线程时,必须首先从CWinThread 派生类,并使用 DECLARE_DYNCREATE 和 IMPLEMENT_DYNCREATE 宏声明此类。
下面给出了CWinThread类的原型(添加了关于其重要函数功能和是否需要被继承类重载的注释):
|
class CWinThread : public CCmdTarget
{
DECLARE_DYNAMIC(CWinThread)
public:
// Constructors
CWinThread();
BOOL CreateThread(DWORD dwCreateFlags = 0, UINT
nStackSize = 0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL);
// Attributes
CWnd* m_pMainWnd; // main window (usually same
AfxGetApp()->m_pMainWnd)
CWnd* m_pActiveWnd; // active main window (may
not be m_pMainWnd)
BOOL m_bAutoDelete; // enables 'delete this'
after thread termination
// only valid while running
HANDLE m_hThread; // this thread's HANDLE
operator HANDLE() const;
DWORD m_nThreadID; // this thread's ID
int GetThreadPriority();
BOOL SetThreadPriority(int nPriority);
// Operations
DWORD SuspendThread();
DWORD ResumeThread();
BOOL PostThreadMessage(UINT message, WPARAM
wParam, LPARAM lParam);
// Overridables
//执行线程实例初始化,必须重写
virtual BOOL InitInstance();
// running and idle processing
//控制线程的函数,包含消息泵,一般不重写
virtual int Run();
//消息调度到TranslateMessage和DispatchMessage之前对其进行筛选,
//通常不重写
virtual BOOL PreTranslateMessage(MSG* pMsg);
virtual BOOL PumpMessage(); // low level message
pump
//执行线程特定的闲置时间处理,通常不重写
virtual BOOL OnIdle(LONG lCount); // return TRUE
if more idle processing
virtual BOOL IsIdleMessage(MSG* pMsg); // checks
for special messages
//线程终止时执行清除,通常需要重写
virtual int ExitInstance(); // default will
'delete this'
//截获由线程的消息和命令处理程序引发的未处理异常,通常不重写
virtual LRESULT
ProcessWndProcException(CException* e, const MSG* pMsg);
// Advanced: handling messages sent to message
filter hook
virtual BOOL ProcessMessageFilter(int code, LPMSG
lpMsg);
// Advanced: virtual access to m_pMainWnd
virtual CWnd* GetMainWnd();
// Implementation
public:
virtual ~CWinThread();
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
int m_nDisablePumpCount; // Diagnostic trap to
detect illegal re-entrancy
#endif
void CommonConstruct();
virtual void Delete();
// 'delete this' only if m_bAutoDelete == TRUE
// message pump for Run
MSG m_msgCur; // current message
public:
// constructor used by implementation of
AfxBeginThread
CWinThread(AFX_THREADPROC pfnThreadProc, LPVOID
pParam);
// valid after construction
LPVOID m_pThreadParams; // generic parameters
passed to starting function
AFX_THREADPROC m_pfnThreadProc;
// set after OLE is initialized
void (AFXAPI* m_lpfnOleTermOrFreeLib)(BOOL,
BOOL);
COleMessageFilter* m_pMessageFilter;
protected:
CPoint m_ptCursorLast; // last mouse position
UINT m_nMsgLast; // last mouse message
BOOL DispatchThreadMessageEx(MSG* msg); // helper
void DispatchThreadMessage(MSG* msg); // obsolete
};
|
启动UI线程的AfxBeginThread函数的原型为:
|
CWinThread *AfxBeginThread(
//从CWinThread派生的类的 RUNTIME_CLASS
CRuntimeClass *pThreadClass,
int nPriority = THREAD_PRIORITY_NORMAL,
UINT nStackSize = 0,
DWORD dwCreateFlags = 0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL
);
|
我们可以方便地使用VC++ 6.0类向导定义一个继承自CWinThread的用户线程类。下面给出产生我们自定义的CWinThread子类CMyUIThread的方法。
打开VC++ 6.0类向导,在如下窗口中选择Base
Class类为CWinThread,输入子类名为CMyUIThread,点击"OK"按钮后就产生了类CMyUIThread。
其源代码框架为:
|
/////////////////////////////////////////////////////////////////////////////
// CMyUIThread thread
class CMyUIThread : public CWinThread
{
DECLARE_DYNCREATE(CMyUIThread)
protected:
CMyUIThread(); // protected constructor used by
dynamic creation
// Attributes
public:
// Operations
public:
// Overrides
// ClassWizard generated virtual function
overrides
//{{AFX_VIRTUAL(CMyUIThread)
public:
virtual BOOL InitInstance();
virtual int ExitInstance();
//}}AFX_VIRTUAL
// Implementation
protected:
virtual ~CMyUIThread();
// Generated message map functions
//{{AFX_MSG(CMyUIThread)
// NOTE - the ClassWizard will add and remove member
functions here.
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
/////////////////////////////////////////////////////////////////////////////
// CMyUIThread
IMPLEMENT_DYNCREATE(CMyUIThread, CWinThread)
CMyUIThread::CMyUIThread()
{}
CMyUIThread::~CMyUIThread()
{}
BOOL CMyUIThread::InitInstance()
{
// TODO: perform. and per-thread initialization
here
return TRUE;
}
int CMyUIThread::ExitInstance()
{
// TODO: perform. any per-thread cleanup here
return CWinThread::ExitInstance();
}
BEGIN_MESSAGE_MAP(CMyUIThread, CWinThread)
//{{AFX_MSG_MAP(CMyUIThread)
// NOTE - the ClassWizard will add and remove mapping macros here.
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
|
使用下列代码就可以启动这个UI线程:
|
CMyUIThread *pThread;
pThread = (CMyUIThread*)
AfxBeginThread( RUNTIME_CLASS(CMyUIThread) );
|
另外,我们也可以不用AfxBeginThread 创建线程,而是分如下两步完成:
(1)调用线程类的构造函数创建一个线程对象;
(2)调用CWinThread::CreateThread函数来启动该线程。
在线程自身内调用AfxEndThread函数可以终止该线程:
|
void AfxEndThread(
UINT nExitCode //the exit code
of the thread
);
|
对于UI线程而言,如果消息队列中放入了WM_QUIT消息,将结束线程。
关于UI线程和工作者线程的分配,最好的做法是:将所有与UI相关的操作放入主线程,其它的纯粹的运算工作交给独立的数个工作者线程。
候捷先生早些时间喜欢为MDI程序的每个窗口创建一个线程,他后来澄清了这个错误。因为如果为MDI程序的每个窗口都单独创建一个线程,在窗口进行切换的时候,将进行线程的上下文切换!
2.线程间通信
MFC中定义了继承自CSyncObject类的CCriticalSection 、CCEvent、CMutex、CSemaphore类封装和简化了WIN32 API所提供的临界区、事件、互斥和信号量。使用这些同步机制,必须包含"Afxmt.h"头文件。下图给出了类的继承关系:
作为CSyncObject类的继承类,我们仅仅使用基类CSyncObject的接口函数就可以方便、统一的操作CCriticalSection
、CCEvent、CMutex、CSemaphore类,下面是CSyncObject类的原型:
|
class CSyncObject : public CObject
{
DECLARE_DYNAMIC(CSyncObject)
// Constructor
public:
CSyncObject(LPCTSTR pstrName);
// Attributes
public:
operator HANDLE() const;
HANDLE m_hObject;
// Operations
virtual BOOL Lock(DWORD dwTimeout = INFINITE);
virtual BOOL Unlock() = 0;
virtual BOOL Unlock(LONG /* lCount */, LPLONG /*
lpPrevCount=NULL */)
{ return TRUE; }
// Implementation
public:
virtual ~CSyncObject();
#ifdef _DEBUG
CString m_strName;
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif
friend class CSingleLock;
friend class CMultiLock;
};
|
CSyncObject类最主要的两个函数是Lock和Unlock,若我们直接使用CSyncObject类及其派生类,我们需要非常小心地在Lock之后调用Unlock。
MFC提供的另两个类CSingleLock(等待一个对象)和CMultiLock(等待多个对象)为我们编写应用程序提供了更灵活的机制,下面以实际来阐述CSingleLock的用法:
|
class CThreadSafeWnd
{
public:
CThreadSafeWnd(){}
~CThreadSafeWnd(){}
void SetWindow(CWnd *pwnd)
{
m_pCWnd = pwnd;
}
void PaintBall(COLORREF color, CRect &rc);
private:
CWnd *m_pCWnd;
CCriticalSection m_CSect;
};
void CThreadSafeWnd::PaintBall(COLORREF color, CRect &rc)
{
CSingleLock csl(&m_CSect);
//缺省的Timeout是INFINITE,只有m_Csect被激活,csl.Lock()才能返回
//true,这里一直等待
if (csl.Lock())
;
{
// not necessary
//AFX_MANAGE_STATE(AfxGetStaticModuleState( ));
CDC *pdc = m_pCWnd->GetDC();
CBrush brush(color);
CBrush *oldbrush =
pdc->SelectObject(&brush);
pdc->Ellipse(rc);
pdc->SelectObject(oldbrush);
GdiFlush(); // don't wait to update the display
}
}
|
上述实例讲述了用CSingleLock对Windows GDI相关对象进行保护的方法,下面再给出一个其他方面的例子:
|
int array1[10], array2[10];
CMutexSection section; //创建一个CMutex类的对象
//赋值线程控制函数
UINT EvaluateThread(LPVOID param)
{
CSingleLock singlelock;
singlelock(§ion);
//互斥区域
singlelock.Lock();
for (int i = 0; i < 10; i++)
array1[i] = i;
singlelock.Unlock();
}
//拷贝线程控制函数
UINT CopyThread(LPVOID param)
{
CSingleLock singlelock;
singlelock(§ion);
//互斥区域
singlelock.Lock();
for (int i = 0; i < 10; i++)
array2[i] = array1[i];
singlelock.Unlock();
}
}
AfxBeginThread(EvaluateThread, NULL); //启动赋值线程
AfxBeginThread(CopyThread, NULL); //启动拷贝线程
|
上面的例子中启动了两个线程EvaluateThread和 CopyThread,线程EvaluateThread把10个数赋值给数组array1[],线程CopyThread将数组array1[]拷贝给 数组array2[]。由于数组的拷贝和赋值都是整体行为,如果不以互斥形式执行代码段:
|
for (int i = 0; i < 10; i++)
array1[i] = i;
|
和
|
for (int i = 0; i < 10; i++)
array2[i] = array1[i];
|
其结果是很难预料的!
除了可使用CCriticalSection、CEvent、CMutex、CSemaphore作为线程间同步通信的方式以外,我们还可以利用PostThreadMessage函数在线程间发送消息:
|
BOOL PostThreadMessage(DWORD idThread, // thread
identifier
UINT Msg, // message to post
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
);
|
3.线程与消息队列
在WIN32中,每一个线程都对应着一个消息队列。由于一个线程可以产生数个窗口,所以并不是每个窗口都对应着一个消息队列。下列几句话应该作为"定理"被记住:
"定理" 一
所有产生给某个窗口的消息,都先由创建这个窗口的线程处理;
"定理" 二
Windows屏幕上的每一个控件都是一个窗口,有对应的窗口函数。
消息的发送通常有两种方式,一是SendMessage,一是PostMessage,其原型分别为:
|
LRESULT SendMessage(HWND hWnd, // handle of
destination window
UINT Msg, // message to send
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
);
BOOL PostMessage(HWND hWnd, // handle of destination window
UINT Msg, // message to post
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
);
|
两个函数原型中的四个参数的意义相同,但是 SendMessage和PostMessage的行为有差异。SendMessage必须等待消息被处理后才返回,而PostMessage仅仅将消息 放入消息队列。SendMessage的目标窗口如果属于另一个线程,则会发生线程上下文切换,等待另一线程处理完成消息。为了防止另一线程当掉,导致 SendMessage永远不能返回,我们可以调用SendMessageTimeout函数:
|
LRESULT SendMessageTimeout(
HWND hWnd, // handle of
destination window
UINT Msg, // message to send
WPARAM wParam, // first message parameter
LPARAM lParam, // second message parameter
UINT fuFlags, // how to send the message
UINT uTimeout, // time-out duration
LPDWORD lpdwResult // return value for synchronous
call
);
|
4. MFC线程、消息队列与MFC程序的"生死因果"
分析MFC程序的主线程启动及消息队列处理的过程将有助于我们进一步理解UI线程与消息队列的关系,为此我们需要简单地叙述一下MFC程序的"生死因果"(侯捷:《深入浅出MFC》)。
使用VC++ 6.0的向导完成一个最简单的单文档架构MFC应用程序MFCThread:
(1) 输入MFC EXE工程名MFCThread;
(2) 选择单文档架构,不支持Document/View结构;
(3) ActiveX、3D container等其他选项都选择无。
我们来分析这个工程。下面是产生的核心源代码:
MFCThread.h 文件
|
class CMFCThreadApp : public CWinApp
{
public:
CMFCThreadApp();
// Overrides
// ClassWizard generated virtual function
overrides
//{{AFX_VIRTUAL(CMFCThreadApp)
public:
virtual BOOL InitInstance();
//}}AFX_VIRTUAL
// Implementation
public:
//{{AFX_MSG(CMFCThreadApp)
afx_msg void OnAppAbout();
// NOTE - the ClassWizard will add and remove
member functions here.
// DO NOT EDIT what you see in these blocks of
generated code !
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
|
MFCThread.cpp文件
|
CMFCThreadApp theApp;
/////////////////////////////////////////////////////////////////////////////
// CMFCThreadApp initialization
BOOL CMFCThreadApp::InitInstance()
{
…
CMainFrame* pFrame. = new CMainFrame;
m_pMainWnd = pFrame;
// create and load the frame. with its resources
pFrame->LoadFrame(IDR_MAINFRAME,WS_OVERLAPPEDWINDOW
| FWS_ADDTOTITLE, NULL,NULL);
// The one and only window has been initialized,
so show and update it.
pFrame->ShowWindow(SW_SHOW);
pFrame->UpdateWindow();
return TRUE;
}
|
MainFrm.h文件
|
#include "ChildView.h"
class CMainFrame. public CFrameWnd
{
public:
CMainFrame();
protected:
DECLARE_DYNAMIC(CMainFrame)
// Attributes
public:
// Operations
public:
// Overrides
// ClassWizard generated virtual function
overrides
//{{AFX_VIRTUAL(CMainFrame)
virtual BOOL PreCreateWindow(CREATESTRUCT&
cs);
virtual BOOL OnCmdMsg(UINT nID, int nCode, void*
pExtra, AFX_CMDHANDLERINFO* pHandlerInfo);
//}}AFX_VIRTUAL
// Implementation
public:
virtual ~CMainFrame();
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif
CChildView m_wndView;
// Generated message map functions
protected:
//{{AFX_MSG(CMainFrame)
afx_msg void OnSetFocus(CWnd *pOldWnd);
// NOTE - the ClassWizard will add and remove
member functions here.
// DO NOT EDIT what you see in these blocks of
generated code!
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
|
MainFrm.cpp文件
|
IMPLEMENT_DYNAMIC(CMainFrame, CFrameWnd)
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
//{{AFX_MSG_MAP(CMainFrame)
// NOTE - the ClassWizard will add and remove
mapping macros here.
// DO NOT EDIT what you see in these blocks of
generated code !
ON_WM_SETFOCUS()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CMainFrame. construction/destruction
CMainFrame.:CMainFrame()
{
// TODO: add member initialization code here
}
CMainFrame.:~CMainFrame()
{}
BOOL CMainFrame.:PreCreateWindow(CREATESTRUCT& cs)
{
if( !CFrameWnd::PreCreateWindow(cs) )
return FALSE;
// TODO: Modify the Window class or styles here
by modifying
// the CREATESTRUCT cs
cs.dwExStyle. &= ~WS_EX_CLIENTEDGE;
cs.lpszClass = AfxRegisterWndClass(0);
return TRUE;
}
|
ChildView.h文件
|
// CChildView window
class CChildView : public CWnd
{
// Construction
public:
CChildView();
// Attributes
public:
// Operations
public:
// Overrides
// ClassWizard generated virtual function
overrides
//{{AFX_VIRTUAL(CChildView)
protected:
virtual BOOL PreCreateWindow(CREATESTRUCT&
cs);
//}}AFX_VIRTUAL
// Implementation
public:
virtual ~CChildView();
// Generated message map functions
protected:
//{{AFX_MSG(CChildView)
afx_msg void OnPaint();
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
ChildView.cpp文件
// CChildView
CChildView::CChildView()
{}
CChildView::~CChildView()
{}
BEGIN_MESSAGE_MAP(CChildView,CWnd )
//{{AFX_MSG_MAP(CChildView)
ON_WM_PAINT()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CChildView message handlers
BOOL CChildView::PreCreateWindow(CREATESTRUCT& cs)
{
if (!CWnd::PreCreateWindow(cs))
return FALSE;
cs.dwExStyle.|= WS_EX_CLIENTEDGE;
cs.style. &= ~WS_BORDER;
cs.lpszClass =
AfxRegisterWndClass(CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS,::LoadCursor(NULL,
IDC_ARROW),
HBRUSH(COLOR_WINDOW+1),NULL);
return TRUE;
}
void CChildView::OnPaint()
{
CPaintDC dc(this); // device context for painting
// TODO: Add your message handler code here
// Do not call CWnd::OnPaint() for painting
messages
}
|
文件MFCThread.h和MFCThread.cpp 定义和实现的类CMFCThreadApp继承自CWinApp类,而CWinApp类又继承自CWinThread类(CWinThread类又继承自 CCmdTarget类),所以CMFCThread本质上是一个MFC线程类,下图给出了相关的类层次结构:
|

我们提取CWinApp类原型的一部分:
|
class CWinApp : public CWinThread
{
DECLARE_DYNAMIC(CWinApp)
public:
// Constructor
CWinApp(LPCTSTR lpszAppName = NULL);// default
app name
// Attributes
// Startup args (do not change)
HINSTANCE m_hInstance;
HINSTANCE m_hPrevInstance;
LPTSTR m_lpCmdLine;
int m_nCmdShow;
// Running args (can be changed in
InitInstance)
LPCTSTR m_pszAppName; // human readable name
LPCTSTR m_pszExeName; // executable name (no
spaces)
LPCTSTR m_pszHelpFilePath; // default based on
module path
LPCTSTR m_pszProfileName; // default based on
app name
// Overridables
virtual BOOL InitApplication();
virtual BOOL InitInstance();
virtual int ExitInstance(); // return app exit
code
virtual int Run();
virtual BOOL OnIdle(LONG lCount); // return
TRUE if more idle processing
virtual LRESULT
ProcessWndProcException(CException* e,const MSG* pMsg);
public:
virtual ~CWinApp();
protected:
DECLARE_MESSAGE_MAP()
};
|
SDK程序的WinMain
所完成的工作现在由CWinApp 的三个函数完成:
|
virtual BOOL InitApplication();
virtual BOOL InitInstance();
virtual int Run();
|
"CMFCThreadApp
theApp;"语句定义的全局变量theApp是整个程式的application object,每一个MFC 应用程序都有一个。当我们执行MFCThread程序的时候,这个全局变量被构造。theApp 配置完成后,WinMain开始执行。但是程序中并没有WinMain的代码,它在哪里呢?原来MFC早已准备好并由Linker直接加到应用程序代码中的,其原型为(存在于VC++6.0安装目录下提供的APPMODUL.CPP文件中):
|
extern "C" int WINAPI
_tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPTSTR lpCmdLine, int nCmdShow)
{
// call shared/exported
WinMain
return AfxWinMain(hInstance, hPrevInstance,
lpCmdLine, nCmdShow);
}
|
其中调用的AfxWinMain如下(存在于VC++6.0安装目录下提供的WINMAIN.CPP文件中):
|
int AFXAPI AfxWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine, int nCmdShow)
{
ASSERT(hPrevInstance == NULL);
int nReturnCode = -1;
CWinThread* pThread = AfxGetThread();
CWinApp* pApp = AfxGetApp();
// AFX internal initialization
if (!AfxWinInit(hInstance, hPrevInstance,
lpCmdLine, nCmdShow))
goto InitFailure;
// App global initializations (rare)
if (pApp != NULL &&
!pApp->InitApplication())
goto InitFailure;
// Perform. specific initializations
if (!pThread->InitInstance())
{
if (pThread->m_pMainWnd != NULL)
{
TRACE0("Warning: Destroying non-NULL
m_pMainWnd\n");
pThread->m_pMainWnd->DestroyWindow();
}
nReturnCode = pThread->ExitInstance();
goto InitFailure;
}
nReturnCode = pThread->Run();
InitFailure:
#ifdef _DEBUG
// Check for missing AfxLockTempMap calls
if (AfxGetModuleThreadState()->m_nTempMapLock
!= 0)
{
TRACE1("Warning: Temp map lock count
non-zero (%ld).\n",
AfxGetModuleThreadState()->m_nTempMapLock);
}
AfxLockTempMaps();
AfxUnlockTempMaps(-1);
#endif
AfxWinTerm();
return nReturnCode;
}
|
我们提取主干,实际上,这个函数做的事情主要是:
|
CWinThread* pThread = AfxGetThread();
CWinApp* pApp = AfxGetApp();
AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow)
pApp->InitApplication()
pThread->InitInstance()
pThread->Run();
|
其中,InitApplication 是注册窗口类别的场所;InitInstance是产生窗口并显示窗口的场所;Run是提取并分派消息的场所。这样,MFC就同WIN32 SDK程序对应起来了。CWinThread::Run是程序生命的"活水源头"(侯捷:《深入浅出MFC》,函数存在于VC++ 6.0安装目录下提供的THRDCORE.CPP文件中):
|
// main running routine until thread exits
int CWinThread::Run()
{
ASSERT_VALID(this);
// for tracking the idle time state
BOOL bIdle = TRUE;
LONG lIdleCount = 0;
// acquire and dispatch messages until a WM_QUIT
message is received.
for (;;)
{
// phase1: check to see if we can do idle work
while (bIdle &&
!::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE))
{
// call OnIdle while in bIdle state
if (!OnIdle(lIdleCount++))
bIdle = FALSE; // assume "no idle"
state
}
// phase2: pump messages while available
do
{
// pump message, but quit on WM_QUIT
if (!PumpMessage())
return ExitInstance();
// reset "no idle" state after
pumping "normal" message
if (IsIdleMessage(&m_msgCur))
{
bIdle = TRUE;
lIdleCount = 0;
}
} while (::PeekMessage(&m_msgCur, NULL,
NULL, NULL, PM_NOREMOVE));
}
ASSERT(FALSE); // not reachable
}
|
其中的PumpMessage函数又对应于:
|
/////////////////////////////////////////////////////////////////////////////
// CWinThread implementation helpers
BOOL CWinThread::PumpMessage()
{
ASSERT_VALID(this);
if (!::GetMessage(&m_msgCur, NULL, NULL,
NULL))
{
return FALSE;
}
// process this message
if(m_msgCur.message != WM_KICKIDLE &&
!PreTranslateMessage(&m_msgCur))
{
::TranslateMessage(&m_msgCur);
::DispatchMessage(&m_msgCur);
}
return TRUE;
}
|
因此,忽略IDLE状态,整个RUN的执行提取主干就是:
|
do {
::GetMessage(&msg,...);
PreTranslateMessage{&msg);
::TranslateMessage(&msg);
::DispatchMessage(&msg);
...
} while (::PeekMessage(...));
|
由此,我们建立了MFC消息获取和派生机制与WIN32 SDK程序之间的对应关系。下面继续分析MFC消息的"绕行"过程。
在MFC中,只要是CWnd 衍生类别,就可以拦下任何Windows消息。与窗口无关的MFC类别(例如CDocument 和CWinApp)如果也想处理消息,必须衍生自CCmdTarget,并且只可能收到WM_COMMAND消息。所有能进行MESSAGE_MAP的类都继承自CCmdTarget,如:
MFC中MESSAGE_MAP的定义依赖于以下三个宏:
|
DECLARE_MESSAGE_MAP()
BEGIN_MESSAGE_MAP(
theClass, //Specifies the name
of the class whose message map this is
baseClass //Specifies the name of the base class
of theClass
)
END_MESSAGE_MAP()
|
我们程序中涉及到的有:MFCThread.h、MainFrm.h、ChildView.h文件
|
DECLARE_MESSAGE_MAP()
MFCThread.cpp文件
BEGIN_MESSAGE_MAP(CMFCThreadApp, CWinApp)
//{{AFX_MSG_MAP(CMFCThreadApp)
ON_COMMAND(ID_APP_ABOUT, OnAppAbout)
// NOTE - the ClassWizard will add and remove mapping macros here.
// DO NOT EDIT what you see in these blocks of generated code!
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
MainFrm.cpp文件
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
//{{AFX_MSG_MAP(CMainFrame)
// NOTE - the ClassWizard will add and remove mapping macros here.
// DO NOT EDIT what you see in these blocks of generated code !
ON_WM_SETFOCUS()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
ChildView.cpp文件
BEGIN_MESSAGE_MAP(CChildView,CWnd )
//{{AFX_MSG_MAP(CChildView)
ON_WM_PAINT()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
|
由这些宏,MFC建立了一个消息映射表(消息流动网),按照消息流动网匹配对应的消息处理函数,完成整个消息的"绕行"。
看到这里相信你有这样的疑问:程序定义了CWinApp类的theApp全局变量,可是从来没有调用AfxBeginThread或theApp.CreateThread启动线程呀,theApp对应的线程是怎么启动的?
答:MFC在这里用了很高明的一招。实际上,程序开始运行,第一个线程是由操作系统(OS)启动的,在CWinApp的构造函数里,MFC将theApp"对应"向了这个线程,具体的实现是这样的:
|
CWinApp::CWinApp(LPCTSTR lpszAppName)
{
if (lpszAppName != NULL)
m_pszAppName = _tcsdup(lpszAppName);
else
m_pszAppName = NULL;
// initialize CWinThread state
AFX_MODULE_STATE *pModuleState =
_AFX_CMDTARGET_GETSTATE();
AFX_MODULE_THREAD_STATE *pThreadState =
pModuleState->m_thread;
ASSERT(AfxGetThread() == NULL);
pThreadState->m_pCurrentWinThread = this;
ASSERT(AfxGetThread() == this);
m_hThread = ::GetCurrentThread();
m_nThreadID = ::GetCurrentThreadId();
// initialize CWinApp state
ASSERT(afxCurrentWinApp == NULL); // only one
CWinApp object please
pModuleState->m_pCurrentWinApp = this;
ASSERT(AfxGetApp() == this);
// in non-running state until WinMain
m_hInstance = NULL;
m_pszHelpFilePath = NULL;
m_pszProfileName = NULL;
m_pszRegistryKey = NULL;
m_pszExeName = NULL;
m_pRecentFileList = NULL;
m_pDocManager = NULL;
m_atomApp = m_atomSystemTopic = NULL; //微软懒鬼?或者他认为
//这样连等含义更明确?
m_lpCmdLine = NULL;
m_pCmdInfo = NULL;
// initialize wait cursor state
m_nWaitCursorCount = 0;
m_hcurWaitCursorRestore = NULL;
// initialize current printer state
m_hDevMode = NULL;
m_hDevNames = NULL;
m_nNumPreviewPages = 0; // not specified
(defaults to 1)
// initialize DAO state
m_lpfnDaoTerm = NULL; // will be set if
AfxDaoInit called
// other initialization
m_bHelpMode = FALSE;
m_nSafetyPoolSize = 512; // default size
}
|
很显然,theApp成员变量都被赋予OS启动的这个当前线程相关的值,如代码:
|
m_hThread = ::GetCurrentThread();//theApp的线程句柄等于当前线程句柄
m_nThreadID = ::GetCurrentThreadId();//theApp的线程ID等于当前线程ID
|
所以CWinApp类几乎只是为MFC程序的第一个线程量身定制的,它不需要也不能被AfxBeginThread或 theApp.CreateThread"再次"启动。这就是CWinApp类和theApp全局变量的内涵!如果你要再增加一个UI线程,不要继承类 CWinApp,而应继承类CWinThread。而参考第1节,由于我们一般以主线程(在MFC程序里实际上就是OS启动的第一个线程)处理所有窗口的 消息,所以我们几乎没有再启动UI线程的需求!
深入浅出Win32多线程程序设计之综合实例
本章我们将以工业控制和嵌入式系统中运用极为广泛的串口通信为例讲述多线程的典型应用。
而网络通信也是多线程应用最广泛的领域之一,所以本章的最后一节也将对多线程网络通信进行简短的描述。
1.串口通信
在工业控制系统中,工控机(一般都基于PC Windows平台)经常需要与单片机通过串口进行通信。因此,操作和使用PC的串口成为大多数单片机、嵌入式系统领域工程师必须具备的能力。
串口的使用需要通过三个步骤来完成的:
(1) 打开通信端口;
(2) 初始化串口,设置波特率、数据位、停止位、奇偶校验等参数。为了给读者一个直观的印象,下图从Windows的"控制面板->系统->设备管理器->通信端口(COM1)"打开COM的设置窗口:

(3) 读写串口。
在WIN32平台下,对通信端口进行操作跟基本的文件操作一样。
创建/打开COM资源
下列函数如果调用成功,则返回一个标识通信端口的句柄,否则返回-1:
|
HADLE CreateFile(PCTSTR lpFileName, //通信端口名,如"COM1"
WORD dwDesiredAccess, //对资源的访问类型
WORD dwShareMode, //指定共享模式,COM不能共享,该参数为0
PSECURITY_ATTRIBUTES lpSecurityAttributes,
//安全描述符指针,可为NULL
WORD dwCreationDisposition, //创建方式
WORD dwFlagsAndAttributes, //文件属性,可为NULL
HANDLE hTemplateFile //模板文件句柄,置为NULL
);
|
获得/设置COM属性
下列函数可以获得COM口的设备控制块,从而获得相关参数:
|
BOOL WINAPI GetCommState(
HANDLE hFile, //标识通信端口的句柄
LPDCB lpDCB //指向一个设备控制块(DCB结构)的指针
);
|
如果要调整通信端口的参数,则需要重新配置设备控制块,再用WIN32
API SetCommState()函数进行设置:
|
BOOL SetCommState(
HANDLE hFile, //标识通信端口的句柄
LPDCB lpDCB //指向一个设备控制块(DCB结构)的指针
);
|
DCB结构包含了串口的各项参数设置,如下:
|
typedef struct _DCB
{
// dcb
DWORD DCBlength; // sizeof(DCB)
DWORD BaudRate; // current baud rate
DWORD fBinary: 1; // binary mode, no EOF check
DWORD fParity: 1; // enable parity checking
DWORD fOutxCtsFlow: 1; // CTS output flow
control
DWORD fOutxDsrFlow: 1; // DSR output flow
control
DWORD fDtrControl: 2; // DTR flow control type
DWORD fDsrSensitivity: 1; // DSR sensitivity
DWORD fTXContinueOnXoff: 1; // XOFF continues Tx
DWORD fOutX: 1; // XON/XOFF out flow control
DWORD fInX: 1; // XON/XOFF in flow control
DWORD fErrorChar: 1; // enable error replacement
DWORD fNull: 1; // enable null stripping
DWORD fRtsControl: 2; // RTS flow control
DWORD fAbortOnError: 1; // abort reads/writes on
error
DWORD fDummy2: 17; // reserved
WORD wReserved; // not currently used
WORD XonLim; // transmit XON threshold
WORD XoffLim; // transmit XOFF threshold
BYTE ByteSize; // number of bits/byte, 4-8
BYTE Parity; // 0-4=no,odd,even,mark,space
BYTE StopBits; // 0,1,2 = 1, 1.5, 2
char XonChar; // Tx and Rx XON character
char XoffChar; // Tx and Rx XOFF character
char ErrorChar; // error replacement character
char EofChar; // end of input character
char EvtChar; // received event character
WORD wReserved1; // reserved; do not use
} DCB;
|
读写串口
在读写串口之前,还要用PurgeComm()函数清空缓冲区,并用SetCommMask ()函数设置事件掩模来监视指定通信端口上的事件,其原型为:
|
BOOL SetCommMask(
HANDLE hFile, //标识通信端口的句柄
DWORD dwEvtMask //能够使能的通信事件
);
|
串口上可能发生的事件如下表所示:
|
值
|
事件描述
|
|
EV_BREAK
|
A break was detected on input.
|
|
EV_CTS
|
The CTS (clear-to-send) signal changed state.
|
|
EV_DSR
|
The DSR(data-set-ready) signal changed state.
|
|
EV_ERR
|
A line-status error occurred. Line-status errors
are CE_FRAME, CE_OVERRUN, and CE_RXPARITY.
|
|
EV_RING
|
A ring indicator was detected.
|
|
EV_RLSD
|
The RLSD (receive-line-signal-detect) signal changed
state.
|
|
EV_RXCHAR
|
A character was received and placed in the input
buffer.
|
|
EV_RXFLAG
|
The event character was received and placed in
the input buffer. The event character is specified in the device's DCB
structure, which is applied to a serial port by using the SetCommState
function.
|
|
EV_TXEMPTY
|
The last character in the output buffer was
sent.
|
在设置好事件掩模后,我们就可以利用WaitCommEvent()函数来等待串口上发生事件,其函数原型为:
|
BOOL WaitCommEvent(
HANDLE hFile, //标识通信端口的句柄
LPDWORD lpEvtMask, //指向存放事件标识变量的指针
LPOVERLAPPED lpOverlapped, // 指向overlapped结构
);
|
我们可以在发生事件后,根据相应的事件类型,进行串口的读写操作:
|
BOOL ReadFile(HANDLE hFile, //标识通信端口的句柄
LPVOID lpBuffer, //输入数据Buffer指针
DWORD nNumberOfBytesToRead, // 需要读取的字节数
LPDWORD lpNumberOfBytesRead, //实际读取的字节数指针
LPOVERLAPPED lpOverlapped //指向overlapped结构
);
BOOL WriteFile(HANDLE hFile, //标识通信端口的句柄
LPCVOID lpBuffer, //输出数据Buffer指针
DWORD nNumberOfBytesToWrite, //需要写的字节数
LPDWORD lpNumberOfBytesWritten, //实际写入的字节数指针
LPOVERLAPPED lpOverlapped //指向overlapped结构
);
|
2.工程实例
下面我们用第1节所述API实现一个多线程的串口通信程序。这个例子工程(工程名为MultiThreadCom)的界面很简单,如下图所示:
它是一个多线程的应用程序,包括两个工作者线程,分别处理串口1和串口2。为了简化问题,我们让连接两个串口的电缆只包含RX、TX两根连线(即不以硬件控制RS-232,串口上只会发生EV_TXEMPTY、EV_RXCHAR事件)。
在工程实例的BOOL CMultiThreadComApp::InitInstance()函数中,启动并设置COM1和COM2,其源代码为:
|
BOOL CMultiThreadComApp::InitInstance()
{
AfxEnableControlContainer();
//打开并设置COM1
hComm1=CreateFile("COM1",
GENERIC_READ|GENERIC_WRITE, 0, NULL ,OPEN_EXISTING, 0,NULL);
if (hComm1==(HANDLE)-1)
{
AfxMessageBox("打开COM1失败");
return false;
}
else
{
DCB wdcb;
GetCommState (hComm1,&wdcb);
wdcb.BaudRate=9600;
SetCommState (hComm1,&wdcb);
PurgeComm(hComm1,PURGE_TXCLEAR);
}
//打开并设置COM2
hComm2=CreateFile("COM2",
GENERIC_READ|GENERIC_WRITE, 0, NULL ,OPEN_EXISTING, 0,NULL);
if (hComm2==(HANDLE)-1)
{
AfxMessageBox("打开COM2失败");
return false;
}
else
{
DCB wdcb;
GetCommState (hComm2,&wdcb);
wdcb.BaudRate=9600;
SetCommState (hComm2,&wdcb);
PurgeComm(hComm2,PURGE_TXCLEAR);
}
CMultiThreadComDlg dlg;
m_pMainWnd = &dlg;
int nResponse = dlg.DoModal();
if (nResponse == IDOK)
{
// TODO: Place code here to handle when the
dialog is
// dismissed with OK
}
else if (nResponse == IDCANCEL)
{
// TODO: Place code here to handle when the
dialog is
// dismissed with Cancel
}
return FALSE;
}
|
此后我们在对话框CMultiThreadComDlg的初始化函数OnInitDialog中启动两个分别处理COM1和COM2的线程:
|
BOOL CMultiThreadComDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// Add "About..." menu item to system
menu.
// IDM_ABOUTBOX must be in the system command
range.
ASSERT((IDM_ABOUTBOX & 0xFFF0) ==
IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
{
CString strAboutMenu;
strAboutMenu.LoadString(IDS_ABOUTBOX);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING,
IDM_ABOUTBOX, strAboutMenu);
}
}
// Set the icon for this dialog. The framework
does this automatically
// when the application's main window is not a
dialog
SetIcon(m_hIcon, TRUE); // Set big icon
SetIcon(m_hIcon, FALSE); // Set small icon
// TODO: Add extra initialization here
//启动串口1处理线程
DWORD nThreadId1;
hCommThread1 = ::CreateThread((LPSECURITY_ATTRIBUTES)NULL,
0,
(LPTHREAD_START_ROUTINE)Com1ThreadProcess, AfxGetMainWnd()->m_hWnd, 0,
&nThreadId1);
if (hCommThread1 == NULL)
{
AfxMessageBox("创建串口1处理线程失败");
return false;
}
//启动串口2处理线程
DWORD nThreadId2;
hCommThread2 = ::CreateThread((LPSECURITY_ATTRIBUTES)NULL,
0,
(LPTHREAD_START_ROUTINE)Com2ThreadProcess, AfxGetMainWnd()->m_hWnd, 0,
&nThreadId2);
if (hCommThread2 == NULL)
{
AfxMessageBox("创建串口2处理线程失败");
return false;
}
return TRUE; // return TRUE unless you set the
focus to a control
}
|
两个串口COM1和COM2对应的线程处理函数等待串口上发生事件,并根据事件类型和自身缓冲区是否有数据要发送进行相应的处理,其源代码为:
|
DWORD WINAPI Com1ThreadProcess(HWND hWnd//主窗口句柄)
{
DWORD wEven;
char str[10]; //读入数据
SetCommMask(hComm1, EV_RXCHAR | EV_TXEMPTY);
while (TRUE)
{
WaitCommEvent(hComm1, &wEven, NULL);
if(wEven = 0)
{
CloseHandle(hCommThread1);
hCommThread1 = NULL;
ExitThread(0);
}
else
{
switch (wEven)
{
case EV_TXEMPTY:
if (wTxPos < wTxLen)
{
//在串口1写入数据
DWORD wCount; //写入的字节数
WriteFile(hComm1, com1Data.TxBuf[wTxPos],
1, &wCount, NULL);
com1Data.wTxPos++;
}
break;
case EV_RXCHAR:
if (com1Data.wRxPos < com1Data.wRxLen)
{
//读取串口数据, 处理收到的数据
DWORD wCount; //读取的字节数
ReadFile(hComm1, com1Data.RxBuf[wRxPos], 1,
&wCount, NULL);
com1Data.wRxPos++;
if(com1Data.wRxPos== com1Data.wRxLen);
::PostMessage(hWnd, COM_SENDCHAR, 0, 1);
}
break;
}
}
}
}
return TRUE;
}
DWORD WINAPI Com2ThreadProcess(HWND hWnd //主窗口句柄)
{
DWORD wEven;
char str[10]; //读入数据
SetCommMask(hComm2, EV_RXCHAR | EV_TXEMPTY);
while (TRUE)
{
WaitCommEvent(hComm2, &wEven, NULL);
if (wEven = 0)
{
CloseHandle(hCommThread2);
hCommThread2 = NULL;
ExitThread(0);
}
else
{
switch (wEven)
{
case EV_TXEMPTY:
if (wTxPos < wTxLen)
{
//在串口2写入数据
DWORD wCount; //写入的字节数
WriteFile(hComm2, com2Data.TxBuf[wTxPos],
1, &wCount, NULL);
com2Data.wTxPos++;
}
break;
case EV_RXCHAR:
if (com2Data.wRxPos < com2Data.wRxLen)
{
//读取串口数据, 处理收到的数据
DWORD wCount; //读取的字节数
ReadFile(hComm2, com2Data.RxBuf[wRxPos], 1,
&wCount, NULL);
com2Data.wRxPos++;
if(com2Data.wRxPos== com2Data.wRxLen);
::PostMessage(hWnd, COM_SENDCHAR, 0, 1);
}
break;
}
}
}
return TRUE;
}
|
线程控制函数中所操作的com1Data和com2Data是与串口对应的数据结构struct tagSerialPort的实例,这个数据结构是:
|
typedef struct tagSerialPort
{
BYTE RxBuf[SPRX_BUFLEN];//接收Buffer
WORD wRxPos; //当前接收字节位置
WORD wRxLen; //要接收的字节数
BYTE TxBuf[SPTX_BUFLEN];//发送Buffer
WORD wTxPos; //当前发送字节位置
WORD wTxLen; //要发送的字节数
}SerialPort, * LPSerialPort;
|
3.多线程串口类
使用多线程串口通信更方便的途径是编写一个多线程的串口类,例如Remon Spekreijse编写了一个CSerialPort串口类。仔细分析这个类的源代码,将十分有助于我们对先前所学多线程及同步知识的理解。
3.1类的定义
|
#ifndef __SERIALPORT_H__
#define __SERIALPORT_H__
#define WM_COMM_BREAK_DETECTED WM_USER+1 // A break was detected on
input.
#define WM_COMM_CTS_DETECTED WM_USER+2 // The CTS (clear-to-send) signal
changed state.
#define WM_COMM_DSR_DETECTED WM_USER+3 // The DSR (data-set-ready) signal
changed state.
#define WM_COMM_ERR_DETECTED WM_USER+4 // A line-status error occurred.
Line-status errors are CE_FRAME, CE_OVERRUN, and CE_RXPARITY.
#define WM_COMM_RING_DETECTED WM_USER+5 // A ring indicator was detected.
#define WM_COMM_RLSD_DETECTED WM_USER+6 // The RLSD
(receive-line-signal-detect) signal changed state.
#define WM_COMM_RXCHAR WM_USER+7 // A character was received and placed
in the input buffer.
#define WM_COMM_RXFLAG_DETECTED WM_USER+8 // The event character was
received and placed in the input buffer.
#define WM_COMM_TXEMPTY_DETECTED WM_USER+9 // The last character in the
output buffer was sent.
class CSerialPort
{
public:
// contruction and destruction
CSerialPort();
virtual ~CSerialPort();
// port initialisation
BOOL InitPort(CWnd* pPortOwner, UINT portnr =
1, UINT baud = 19200, char parity = 'N', UINT databits = 8, UINT
stopsbits = 1, DWORD dwCommEvents = EV_RXCHAR | EV_CTS, UINT nBufferSize
= 512);
// start/stop comm watching
BOOL StartMonitoring();
BOOL RestartMonitoring();
BOOL StopMonitoring();
DWORD GetWriteBufferSize();
DWORD GetCommEvents();
DCB GetDCB();
void WriteToPort(char* string);
protected:
// protected memberfunctions
void ProcessErrorMessage(char* ErrorText);
static UINT CommThread(LPVOID pParam);
static void ReceiveChar(CSerialPort* port,
COMSTAT comstat);
static void WriteChar(CSerialPort* port);
// thread
CWinThread* m_Thread;
// synchronisation objects
CRITICAL_SECTION m_csCommunicationSync;
BOOL m_bThreadAlive;
// handles
HANDLE m_hShutdownEvent;
HANDLE m_hComm;
HANDLE m_hWriteEvent;
// Event array.
// One element is used for each event. There
are two event handles for each port.
// A Write event and a receive character event
which is located in the overlapped structure (m_ov.hEvent).
// There is a general shutdown when the port is
closed.
HANDLE m_hEventArray[3];
// structures
OVERLAPPED m_ov;
COMMTIMEOUTS m_CommTimeouts;
DCB m_dcb;
// owner window
CWnd* m_pOwner;
// misc
UINT m_nPortNr;
char* m_szWriteBuffer;
DWORD m_dwCommEvents;
DWORD m_nWriteBufferSize;
};
#endif __SERIALPORT_H__
|
3.2类的实现
3.2.1构造函数与析构函数
进行相关变量的赋初值及内存恢复:
|
CSerialPort::CSerialPort()
{
m_hComm = NULL;
// initialize overlapped structure members to
zero
m_ov.Offset = 0;
m_ov.OffsetHigh = 0;
// create events
m_ov.hEvent = NULL;
m_hWriteEvent = NULL;
m_hShutdownEvent = NULL;
m_szWriteBuffer = NULL;
m_bThreadAlive = FALSE;
}
//
// Delete dynamic memory
//
CSerialPort::~CSerialPort()
{
do
{
SetEvent(m_hShutdownEvent);
}
while (m_bThreadAlive);
TRACE("Thread ended\n");
delete []m_szWriteBuffer;
}
|
3.2.2核心函数:初始化串口
在初始化串口函数中,将打开串口,设置相关参数,并创建串口相关的用户控制事件,初始化临界区(Critical
Section),以成队的EnterCriticalSection()、LeaveCriticalSection()函数进行资源的排它性访问:
|
BOOL CSerialPort::InitPort(CWnd *pPortOwner,
// the owner (CWnd) of the port (receives message)
UINT portnr, // portnumber (1..4)
UINT baud, // baudrate
char parity, // parity
UINT databits, // databits
UINT stopbits, // stopbits
DWORD dwCommEvents, // EV_RXCHAR, EV_CTS etc
UINT writebuffersize) // size to the writebuffer
{
assert(portnr > 0
&& portnr < 5);
assert(pPortOwner != NULL);
// if the thread is alive: Kill
if (m_bThreadAlive)
{
do
{
SetEvent(m_hShutdownEvent);
}
while (m_bThreadAlive);
TRACE("Thread ended\n");
}
// create events
if (m_ov.hEvent != NULL)
ResetEvent(m_ov.hEvent);
m_ov.hEvent = CreateEvent(NULL, TRUE, FALSE,
NULL);
if (m_hWriteEvent != NULL)
ResetEvent(m_hWriteEvent);
m_hWriteEvent = CreateEvent(NULL, TRUE, FALSE,
NULL);
if (m_hShutdownEvent != NULL)
ResetEvent(m_hShutdownEvent);
m_hShutdownEvent = CreateEvent(NULL, TRUE,
FALSE, NULL);
// initialize the event objects
m_hEventArray[0] = m_hShutdownEvent; // highest
priority
m_hEventArray[1] = m_ov.hEvent;
m_hEventArray[2] = m_hWriteEvent;
// initialize critical section
InitializeCriticalSection(&m_csCommunicationSync);
// set buffersize for writing and save the owner
m_pOwner = pPortOwner;
if (m_szWriteBuffer != NULL)
delete []m_szWriteBuffer;
m_szWriteBuffer = new char[writebuffersize];
m_nPortNr = portnr;
m_nWriteBufferSize = writebuffersize;
m_dwCommEvents = dwCommEvents;
BOOL bResult = FALSE;
char *szPort = new char[50];
char *szBaud = new char[50];
// now it critical!
EnterCriticalSection(&m_csCommunicationSync);
// if the port is already opened: close it
if (m_hComm != NULL)
{
CloseHandle(m_hComm);
m_hComm = NULL;
}
// prepare port strings
sprintf(szPort, "COM%d", portnr);
sprintf(szBaud, "baud=%d parity=%c data=%d
stop=%d", baud, parity, databits,stopbits);
// get a handle to the port
m_hComm = CreateFile(szPort, // communication
port string (COMX)
GENERIC_READ | GENERIC_WRITE, // read/write
types
0, // comm devices must be opened with
exclusive access
NULL, // no security attributes
OPEN_EXISTING, // comm devices must use
OPEN_EXISTING
FILE_FLAG_OVERLAPPED, // Async I/O
0); // template must be 0 for comm devices
if (m_hComm == INVALID_HANDLE_VALUE)
{
// port not found
delete []szPort;
delete []szBaud;
return FALSE;
}
// set the timeout values
m_CommTimeouts.ReadIntervalTimeout = 1000;
m_CommTimeouts.ReadTotalTimeoutMultiplier =
1000;
m_CommTimeouts.ReadTotalTimeoutConstant = 1000;
m_CommTimeouts.WriteTotalTimeoutMultiplier =
1000;
m_CommTimeouts.WriteTotalTimeoutConstant = 1000;
// configure
if (SetCommTimeouts(m_hComm,
&m_CommTimeouts))
{
if (SetCommMask(m_hComm, dwCommEvents))
{
if (GetCommState(m_hComm, &m_dcb))
{
m_dcb.fRtsControl = RTS_CONTROL_ENABLE; //
set RTS bit high!
if (BuildCommDCB(szBaud, &m_dcb))
{
if (SetCommState(m_hComm, &m_dcb))
;
// normal operation... continue
else
ProcessErrorMessage("SetCommState()");
}
else
ProcessErrorMessage("BuildCommDCB()");
}
else
ProcessErrorMessage("GetCommState()");
}
else
ProcessErrorMessage("SetCommMask()");
}
else
ProcessErrorMessage("SetCommTimeouts()");
delete []szPort;
delete []szBaud;
// flush the port
PurgeComm(m_hComm, PURGE_RXCLEAR | PURGE_TXCLEAR
| PURGE_RXABORT | PURGE_TXABORT);
// release critical section
LeaveCriticalSection(&m_csCommunicationSync);
TRACE("Initialisation for communicationport
%d completed.\nUse Startmonitor to communicate.\n", portnr);
return TRUE;
}
|
3.3.3核心函数:串口线程控制函数
串口线程处理函数是整个类中最核心的部分,它主要完成两类工作:
(1)利用WaitCommEvent函数对串口上发生的事件进行获取并根据事件的不同类型进行相应的处理;
(2)利用WaitForMultipleObjects函数对串口相关的用户控制事件进行等待并做相应处理。
|
UINT CSerialPort::CommThread(LPVOID pParam)
{
// Cast the void pointer
passed to the thread back to
// a pointer of CSerialPort class
CSerialPort *port = (CSerialPort*)pParam;
// Set the status variable in the dialog class
to
// TRUE to indicate the thread is running.
port->m_bThreadAlive = TRUE;
// Misc. variables
DWORD BytesTransfered = 0;
DWORD Event = 0;
DWORD CommEvent = 0;
DWORD dwError = 0;
COMSTAT comstat;
BOOL bResult = TRUE;
// Clear comm buffers at startup
if (port->m_hComm)
// check if the port is opened
PurgeComm(port->m_hComm, PURGE_RXCLEAR |
PURGE_TXCLEAR | PURGE_RXABORT | PURGE_TXABORT);
// begin forever loop. This loop will run as
long as the thread is alive.
for (;;)
{
// Make a call to WaitCommEvent(). This call
will return immediatly
// because our port was created as an async
port (FILE_FLAG_OVERLAPPED
// and an m_OverlappedStructerlapped structure
specified). This call will cause the
// m_OverlappedStructerlapped element
m_OverlappedStruct.hEvent, which is part of the m_hEventArray to
// be placed in a non-signeled state if there
are no bytes available to be read,
// or to a signeled state if there are bytes
available. If this event handle
// is set to the non-signeled state, it will
be set to signeled when a
// character arrives at the port.
// we do this for each port!
bResult = WaitCommEvent(port->m_hComm,
&Event, &port->m_ov);
if (!bResult)
{
// If WaitCommEvent() returns FALSE, process
the last error to determin
// the reason..
switch (dwError = GetLastError())
{
case ERROR_IO_PENDING:
{
// This is a normal return value if there
are no bytes
// to read at the port.
// Do nothing and continue
break;
}
case 87:
{
// Under Windows NT, this value is returned
for some reason.
// I have not investigated why, but it is
also a valid reply
// Also do nothing and continue.
break;
}
default:
{
// All other error codes indicate a serious
error has
// occured. Process this error.
port->ProcessErrorMessage("WaitCommEvent()");
break;
}
}
}
else
{
// If WaitCommEvent() returns TRUE, check to
be sure there are
// actually bytes in the buffer to read.
//
// If you are reading more than one byte at a
time from the buffer
// (which this program does not do) you will
have the situation occur
// where the first byte to arrive will cause
the WaitForMultipleObjects()
// function to stop waiting. The
WaitForMultipleObjects() function
// resets the event handle in
m_OverlappedStruct.hEvent to the non-signelead state
// as it returns.
//
// If in the time between the reset of this
event and the call to
// ReadFile() more bytes arrive, the
m_OverlappedStruct.hEvent handle will be set again
// to the signeled state. When the call to
ReadFile() occurs, it will
// read all of the bytes from the buffer, and
the program will
// loop back around to WaitCommEvent().
//
// At this point you will be in the situation
where m_OverlappedStruct.hEvent is set,
// but there are no bytes available to read.
If you proceed and call
// ReadFile(), it will return immediatly due
to the async port setup, but
// GetOverlappedResults() will not return
until the next character arrives.
//
// It is not desirable for the
GetOverlappedResults() function to be in
// this state. The thread shutdown event
(event 0) and the WriteFile()
// event (Event2) will not work if the thread
is blocked by GetOverlappedResults().
//
// The solution to this is to check the
buffer with a call to ClearCommError().
// This call will reset the event handle, and
if there are no bytes to read
// we can loop back through WaitCommEvent()
again, then proceed.
// If there are really bytes to read, do
nothing and proceed.
bResult = ClearCommError(port->m_hComm,
&dwError, &comstat);
if (comstat.cbInQue == 0)
continue;
} // end if bResult
// Main wait function. This function will
normally block the thread
// until one of nine events occur that require
action.
Event = WaitForMultipleObjects(3,
port->m_hEventArray, FALSE, INFINITE);
switch (Event)
{
case 0:
{
// Shutdown event. This is event zero so it
will be
// the higest priority and be serviced
first.
port->m_bThreadAlive = FALSE;
// Kill this thread. break is not needed,
but makes me feel better.
AfxEndThread(100);
break;
}
case 1:
// read event
{
GetCommMask(port->m_hComm,
&CommEvent);
if (CommEvent &EV_CTS)
::SendMessage(port->m_pOwner->m_hWnd,
WM_COMM_CTS_DETECTED, (WPARAM)0, (LPARAM)port->m_nPortNr);
if (CommEvent &EV_RXFLAG)
::SendMessage(port->m_pOwner->m_hWnd,
WM_COMM_RXFLAG_DETECTED,(WPARAM)0, (LPARAM)port->m_nPortNr);
if (CommEvent &EV_BREAK)
::SendMessage(port->m_pOwner->m_hWnd,
WM_COMM_BREAK_DETECTED,(WPARAM)0, (LPARAM)port->m_nPortNr);
if (CommEvent &EV_ERR)
::SendMessage(port->m_pOwner->m_hWnd,
WM_COMM_ERR_DETECTED, (WPARAM)0, (LPARAM)port->m_nPortNr);
if (CommEvent &EV_RING)
::SendMessage(port->m_pOwner->m_hWnd,
WM_COMM_RING_DETECTED,(WPARAM)0, (LPARAM)port->m_nPortNr);
if (CommEvent &EV_RXCHAR)
// Receive character event from port.
ReceiveChar(port, comstat);
break;
}
case 2:
// write event
{
// Write character event from port
WriteChar(port);
break;
}
} // end switch
} // close forever loop
return 0;
}
|
下列三个函数用于对串口线程进行启动、挂起和恢复:
|
//
// start comm watching
//
BOOL CSerialPort::StartMonitoring()
{
if (!(m_Thread =
AfxBeginThread(CommThread, this)))
return FALSE;
TRACE("Thread started\n");
return TRUE;
}
//
// Restart the comm thread
//
BOOL CSerialPort::RestartMonitoring()
{
TRACE("Thread resumed\n");
m_Thread->ResumeThread();
return TRUE;
}
//
// Suspend the comm thread
//
BOOL CSerialPort::StopMonitoring()
{
TRACE("Thread suspended\n");
m_Thread->SuspendThread();
return TRUE;
}
|
3.3.4读写串口
下面一组函数是用户对串口进行读写操作的接口:
|
//
// Write a character.
//
void CSerialPort::WriteChar(CSerialPort *port)
{
BOOL bWrite = TRUE;
BOOL bResult = TRUE;
DWORD BytesSent = 0;
ResetEvent(port->m_hWriteEvent);
// Gain ownership of the critical section
EnterCriticalSection(&port->m_csCommunicationSync);
if (bWrite)
{
// Initailize variables
port->m_ov.Offset = 0;
port->m_ov.OffsetHigh = 0;
// Clear buffer
PurgeComm(port->m_hComm, PURGE_RXCLEAR |
PURGE_TXCLEAR | PURGE_RXABORT | PURGE_TXABORT);
bResult = WriteFile(port->m_hComm, // Handle
to COMM Port
port->m_szWriteBuffer, // Pointer to
message buffer in calling finction
strlen((char*)port->m_szWriteBuffer), //
Length of message to send
&BytesSent, // Where to store the number
of bytes sent
&port->m_ov); // Overlapped structure
// deal with any error codes
if (!bResult)
{
DWORD dwError = GetLastError();
switch (dwError)
{
case ERROR_IO_PENDING:
{
// continue to GetOverlappedResults()
BytesSent = 0;
bWrite = FALSE;
break;
}
default:
{
// all other error codes
port->ProcessErrorMessage("WriteFile()");
}
}
}
else
{
LeaveCriticalSection(&port->m_csCommunicationSync);
}
} // end if(bWrite)
if (!bWrite)
{
bWrite = TRUE;
bResult = GetOverlappedResult(port->m_hComm,
// Handle to COMM port
&port->m_ov, // Overlapped structure
&BytesSent, // Stores number of bytes sent
TRUE); // Wait flag
LeaveCriticalSection(&port->m_csCommunicationSync);
// deal with the error code
if (!bResult)
{
port->ProcessErrorMessage("GetOverlappedResults()
in WriteFile()");
}
} // end if (!bWrite)
// Verify that the data size send equals what we
tried to send
if (BytesSent != strlen((char*)port->m_szWriteBuffer))
{
TRACE("WARNING: WriteFile() error.. Bytes
Sent: %d; Message Length: %d\n",
BytesSent,
strlen((char*)port->m_szWriteBuffer));
}
}
//
// Character received. Inform. the owner
//
void CSerialPort::ReceiveChar(CSerialPort *port, COMSTAT comstat)
{
BOOL bRead = TRUE;
BOOL bResult = TRUE;
DWORD dwError = 0;
DWORD BytesRead = 0;
unsigned char RXBuff;
for (;;)
{
// Gain ownership of the comm port critical
section.
// This process guarantees no other part of
this program
// is using the port object.
EnterCriticalSection(&port->m_csCommunicationSync);
// ClearCommError() will update the COMSTAT
structure and
// clear any other errors.
bResult = ClearCommError(port->m_hComm,
&dwError, &comstat);
LeaveCriticalSection(&port->m_csCommunicationSync);
// start forever loop. I use this type of loop
because I
// do not know at runtime how many loops this
will have to
// run. My solution is to start a forever loop
and to
// break out of it when I have processed all of
the
// data available. Be careful with this
approach and
// be sure your loop will exit.
// My reasons for this are not as clear in this
sample
// as it is in my production code, but I have
found this
// solutiion to be the most efficient way to do
this.
if (comstat.cbInQue == 0)
{
// break out when all bytes have been read
break;
}
EnterCriticalSection(&port->m_csCommunicationSync);
if (bRead)
{
bResult = ReadFile(port->m_hComm, // Handle
to COMM port
&RXBuff, // RX Buffer Pointer
1, // Read one byte
&BytesRead, // Stores number of bytes
read
&port->m_ov); // pointer to the m_ov
structure
// deal with the error code
if (!bResult)
{
switch (dwError = GetLastError())
{
case ERROR_IO_PENDING:
{
// asynchronous i/o is still in progress
// Proceed on to GetOverlappedResults();
bRead = FALSE;
break;
}
default:
{
// Another error has occured. Process this
error.
port->ProcessErrorMessage("ReadFile()");
break;
}
}
}
else
{
// ReadFile() returned complete. It is not
necessary to call GetOverlappedResults()
bRead = TRUE;
}
} // close if (bRead)
if (!bRead)
{
bRead = TRUE;
bResult = GetOverlappedResult(port->m_hComm,
// Handle to COMM port
&port->m_ov, // Overlapped structure
&BytesRead, // Stores number of bytes
read
TRUE); // Wait flag
// deal with the error code
if (!bResult)
{
port->ProcessErrorMessage("GetOverlappedResults()
in ReadFile()");
}
} // close if (!bRead)
LeaveCriticalSection(&port->m_csCommunicationSync);
// notify parent that a byte was received
::SendMessage((port->m_pOwner)->m_hWnd,
WM_COMM_RXCHAR, (WPARAM)RXBuff,(LPARAM)port->m_nPortNr);
} // end forever loop
}
//
// Write a string to the port
//
void CSerialPort::WriteToPort(char *string)
{
assert(m_hComm != 0);
memset(m_szWriteBuffer, 0,
sizeof(m_szWriteBuffer));
strcpy(m_szWriteBuffer, string);
// set event for write
SetEvent(m_hWriteEvent);
}
//
// Return the output buffer size
//
DWORD CSerialPort::GetWriteBufferSize()
{
return m_nWriteBufferSize;
}
|
3.3.5控制接口
应用程序员使用下列一组public函数可以获取串口的DCB及串口上发生的事件:
|
//
// Return the device control block
//
DCB CSerialPort::GetDCB()
{
return m_dcb;
}
//
// Return the communication event masks
//
DWORD CSerialPort::GetCommEvents()
{
return m_dwCommEvents;
}
|
3.3.6错误处理
|
//
// If there is a error, give the right message
//
void CSerialPort::ProcessErrorMessage(char *ErrorText)
{
char *Temp = new char[200];
LPVOID lpMsgBuf;
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM,
NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL,
SUBLANG_DEFAULT),
// Default language
(LPTSTR) &lpMsgBuf, 0, NULL);
sprintf(Temp,
"WARNING: %s Failed with the following
error: \n%s\nPort: %d\n", (char*)
ErrorText, lpMsgBuf, m_nPortNr);
MessageBox(NULL, Temp, "Application
Error", MB_ICONSTOP);
LocalFree(lpMsgBuf);
delete []Temp;
}
|
仔细分析Remon Spekreijse的CSerialPort类对我们理解多线程及其同步机制是大有益处的,从
http://codeguru.earthweb.com/network/serialport.shtml我们可以获取CSerialPort类的 介绍与工程实例。另外,电子工业出版社《Visual
C++/Turbo C串口通信编程实践》一书的作者龚建伟也编写了一个使用CSerialPort类的例子,可以从http://www.gjwtech.com /scomm/sc2serialportclass.htm获得详情。
4.多线程网络通信
在网络通信中使用多线程主要有两种途径,即主监控线程和线程池。
4.1主监控线程
这种方式指的是程序中使用一个主线程监控某特定端口,一旦在这个端口上发生连接请求,则主监控线程动态使用CreateThread派生出新的子线程处理该请求。主线程在派生子线程后不再对子线程加以控制和调度,而由子线程独自和客户方发生连接并处理异常。
使用这种方法的优点是:
(1)可以较快地实现原型设计,尤其在用户数目较少、连接保持时间较长时有表现较好;
(2)主线程不与子线程发生通信,在一定程度上减少了系统资源的消耗。
其缺点是:
(1)生成和终止子线程的开销比较大;
(2)对远端用户的控制较弱。
这种多线程方式总的特点是"动态生成,静态调度"。
4.2线程池
这种方式指的是主线程在初始化时静态地生成一定数量的悬挂子线程,放置于线程池中。随后,主线程将对这些悬挂子线程进行动态调度。一旦客户发出连接请求,主线程将从线程池中查找一个悬挂的子线程:
(1)如果找到,主线程将该连接分配给这个被发现的子线程。子线程从主线程处接管该连接,并与用户通信。当连接结束时,该子线程将自动悬挂,并进人线程池等待再次被调度;
(2)如果当前已没有可用的子线程,主线程将通告发起连接的客户。
使用这种方法进行设计的优点是:
(1)主线程可以更好地对派生的子线程进行控制和调度;
(2)对远程用户的监控和管理能力较强。
虽然主线程对子线程的调度要消耗一定的资源,但是与主监控线程方式中派生和终止线程所要耗费的资源相比,要少很多。因此,使用该种方法设计和实现的系统在客户端连接和终止变更频繁时有上佳表现。
这种多线程方式总的特点是"静态生成,动态调度"。
|
|