1. 线程由两部分组成:
<1> 线程内核对象,操作系统用它来对线程实施管理。内核对象也是系统用来存放线程统计信息的地地方
<2>线程堆栈:它用于维护线程在执行代码时需要的所有函数参数和局部变量.线程总是在某个进程环境中创建,系统从进程的地址空间中分配内存,供线程的栈使用.
线程运行:
操作系统为每一个运行线程安排一定的CPU时间—时间片,系统通过一种循环的方式为线程提供时间片,线程在自己的时间内运行,因时间片相当短,因此给用户的感觉就是好像多个线程是同时运行一样.
进程由两部分组成:
<1> 一个是操作系统用来管理进程的内核对象,也是系统用来存放关于进程的统计信息的地方。
<2> 另一个是地址空间,包含所有可执行模块和DLL模块的代码和数据。还包含动态内存分配的空间。如线程堆栈和堆分配空间。
2. 多线程的创建
2.1 Win32 API:
_beginthreadex 代替CreateThread, 完成CRT函数的一些初始化工作,避免内存泄漏。
线程退出: _endthreadex
HANDLE CreateThread (
SEC_ATTRS SecurityAttributes,//安全结构全指针,NULL为默认安全性
ULONG StackSize,//线程初始栈的大小 ,以字节为单位,如果为0或小于0,默认将使用
//与调用该线程相同的栈空间大小
SEC_THREAD_START StartFunction,//线程处理函数地址(可用函数名)
PVOID ThreadParameter,//可以为数据或其它信息的指针,表示给新线程传送参数
ULONG CreationFlags,//线程创建的标记,可以为0或CREATE_SUSPENDED,如果
//CREATE_SUSPENDED线程创建后暂停状态,直到程序调用//ResumeThread,如果为0立即运行
PULONG ThreadId //[out]返回值,系统分配新的线程ID
2.2 创建MFC多线程
AfxBeginThread:MFC中线程创建函数,MFC对CreateThread的封装
两步法创建:
启动线程,不用传递参数。
CUIThread* pThread=new CUIThread();
pThread->CreateThread();
启动线程,传递参数(本窗口句柄,用来接收消息)
CUIThread* pThread=new CUIThread(GetSafeHwnd());
pThread->CreateThread();
多线程分类
MFC种有两类线程,分别称为工作者线程和用户界面线程。
工作者线程没有消息循环和消息机制,通常用来执行后台运算和维护任务,如冗长的计算过程,后台打印。
UI线程具有自己的消息队列和消息循环,一般用于处理独立于其他线程执行之外的用户输入,响应用户及系统所产生的事件和消息等。
(1)WorkThread创建
CWinThread* AfxBeginThread(AFX_THREADPROC pfnThreadProc,
LPVOID pParam,
nPriority=THREAD_PRIORITY_NORMAL,
UINT nStackSize=0,
DWORD dwCreateFlags=0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs=NULL);
(2)UIThread创建
CWinThread* AfxBeginThread(CRuntimeClass* pThreadClass,
int nPriority=THREAD_PRIORITY_NORMAL,
UINT nStackSize=0,
DWORD dwCreateFlags=0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs=NULL);
3. 如何退出线程
若要终止线程的运行,可以使用下面的4种方法:
3.1 线程函数的return返回(最好这样):
其中用线程函数的return返回, 而终止线程是最安全的, 在线程函数return返回后, 会清理函数内申请的类对象, 即调用这些对象的析构函数. 然后会自动调用 _endthreadex()函数来清理 _beginthreadex(...)函数申请的资源(主要是创建的tiddata对象).
如果线程能够返回,可以确保下列事项的实现:
(1)在线程函数中创建的所有C++对象均将通过它们的撤消函数正确地撤消。
(2)操作系统将正确地释放线程堆栈使用的内存。
(3)系统将线程的退出代码(在线程的内核对象中维护)设置为线程函数的返回值。
(4)系统将递减线程内核对象的使用计数。
3.2 调用 _endthreadex()函数(C++运行期库函数) 或 ExitThread()函数(最好不要):
如果使用这两种方法退出线程, 则不会执行线程函数的return语句, 所以就不会调用线程函数作用域内申请的类对象的析构函数, 会造成内存泄露.
VOID ExitThread(DWORD dwExitCode);
该函数将终止线程的运行,并导致操作系统清除该线程使用的所有操作系统资源。但是,C++资源(如C++类对象)将不被撤消。由于这个原因,最好从线程函数返回,而不是通过调用ExitThread来返回。避免调用ExitThread的另一个原因是,它会使得线程的tiddata内存块无法释放,这样,应用程序将会始终占用内存(直到整个进程终止运行为止)。可以调用_endthreadex以便释放tiddata块,然后退出。
如果编写C/C++代码,那么决不应该调用ExitThread。应该使用Visual C++运行期库函数_endthreadex。避免运行期库函数创建的内存未被释放。
3.3 用同一个进程或者另一个进程中的线程调用 TerminateThread()函数(必须避免);
BOOL TerminateThread(
HANDLE hThread,
DWORD dwExitCode);
与ExitThread不同,ExitThread总是撤消调用的线程,而TerminateThread能够撤消任何线程。hThread参数用于标识被终止运行的线程的句柄。当线程终止运行时,它的退出代码成为你作为dwExitCode参数传递的值。同时,线程的内核对象的使用计数也被递减。
如果使用TerminateThread,那么在拥有线程的进程终止运行之前,系统不会撤消该线程的堆栈。这是Microsoft故意实现的。
注意TerminateThread函数是异步运行的函数,也就是说,它告诉系统你想要线程终止运行,但是,当函数返回时,不能保证线程被撤消。如果需要确切地知道该线程已经终止运行,必须调用WaitForSingleObject或者类似的函数,传递线程的句柄。
3.4 包含线程的进程终止运行(绝对避免);
4. 如何挂起线程和恢复线程
DWORD WINAPI SuspendThread( _In_ HANDLE hThread );
线程暂停的最多次数可以是MAXIMUM_SUSPEND_COUNT次(WinNT.h中定义为127)。调用SuspendThread必须小心,如果线程正在试图从堆栈中分配内存,那么暂停线程就会在该堆栈上设置一个锁。当其他线程试图访问该堆栈时,这些线程的访问就被停止,直到第一个线程恢复运行。
DWORD ResumeThread( );
如果在线程中调用Sleep函数:
a, 调用Sleep,可使线程自愿放弃它剩余的时间片
b, 系统将在大约的指定毫秒数内使线程不可调度。
c, 可以调用Sleep,并且为dwMilliseconds参数传递INFINITE。这将告诉系统永远不要调度该线程。这不是一件值得去做的事情。最好是让线程退出,并还原它的堆栈和内核对象。
d, 可以将0传递给Sleep。这将告诉系统,调用线程将释放剩余的时间片,并迫使系统调度另一个线程。
if (m_pThread != NULL)
{
//挂起线程
m_pThread->SuspendThread();
}
if (m_pThread != NULL)
{
//恢复线程
m_pThread->ResumeThread();
}
5. 如何等待线程结束
if (m_pThread != NULL)
{
//等待线程结束
::WaitForSingleObject(m_pThread->m_hThread, INFINITE);
delete m_pThread;
m_pThread = NULL;
}
6. 如何获取线程的退出码
BOOL WINAPI GetExitCodeThread(
_In_ HANDLE hThread,
_Out_ LPDWORD lpExitCode
);
如果尚未终止运行,则返回STILL_ACTIVE标识符(0x103)。
DWORD dwExitCode = 0;
if (::GetExitCodeThread(m_hThread2, &dwExitCode))
{
if (dwExitCode == STILL_ACTIVE)
{
AfxMessageBox(_T("线程正在运行\n"));
}
else
{
CString strText = _T("");
strText.Format("线程退出,退出码:%d\n", dwExitCode);
AfxMessageBox(strText);
}
}
7. 等待线程结束
WaitForSingleObject, 等待一个内核对象:
函数原型为:
DWORD WINAPI WaitForSingleObject(
_In_ HANDLE hHandle,
_In_ DWORD dwMilliseconds
);
WaitForMultipleObjects, 等待多个内核对象:
函数原型为:
DWORD WINAPI WaitForMultipleObjects(
_In_ DWORD nCount,
_In_ const HANDLE *lpHandles,
_In_ BOOL bWaitAll,
_In_ DWORD dwMilliseconds
);
参数解析:
nCount 指定列表中的句柄数量 最大值为MAXIMUM_WAIT_OBJECTS(64)
*lpHandles 句柄数组的指针。lpHandles为指定对象句柄组合中的第一个元素 HANDLE类型可以为(Event,Mutex,Process,Thread,Semaphore)数组
bWaitAll 等待的类型,如果为TRUE,表示除非对象都发出信号,否则就一直等待下去;如果FALSE,表示任何对象发出信号即可
dwMilliseconds指定要等候的毫秒数。如设为零,表示立即返回。如指定常数INFINITE,则可根据实际情况无限等待下去
函数的返回值有:
WAIT_ABANDONED_0:所有对象都发出消息,而且其中有一个或多个属于互斥体(如果占用互斥量的线程在释放互斥量之前终止(使用ExitThread,TerminateThread,ExitProcess,TerminateProcess),就会返回该信号)
WAIT_TIMEOUT:对象保持未发信号的状态,但规定的等待超时时间已经超过
WAIT_OBJECT_0:所有对象都发出信号
WAIT_IO_COMPLETION:(仅适用于WaitForMultipleObjectsEx)由于一个I/O完成操作已作好准备执行,所以造成了函数的返回
返回WAIT_FAILED则表示函数执行失败,会设置GetLastError
如bWaitAll为FALSE,那么返回结果相似,只是可能还会返回相对于WAIT_ABANDONED_0或WAIT_OBJECT_0的一个正偏移量,指出哪个对象是被抛弃还是发出信号。
WAIT_OBJECT_0是微软定义的一个宏,你就把它看成一个数字就可以了。
例如,WAIT_OBJECT_0 + 5的返回结果意味着列表中的第5个对象发出了信号
线程同步将在下一篇文章介绍。
#include <iostream>
#include <windows.h>
using namespace std;
DWORD WINAPI SaleThread1(__in LPVOID lpParameter);
DWORD WINAPI SaleThread2(__in LPVOID lpParameter);
DWORD WINAPI SaleThread3(__in LPVOID lpParameter);
int tickets = 100;
HANDLE g_hMutex = NULL;
int main()
{
HANDLE hThread[3];
g_hMutex = CreateMutex(NULL, FALSE, _T("tickets"));
//TRUE代表主线程拥有互斥对象 但是主线程没有释放该对象 互斥对象谁拥有 谁释放
// FLASE代表当前没有线程拥有这个互斥对象
if (g_hMutex)
{
if (ERROR_ALREADY_EXISTS == GetLastError())
{
cout << "only instance can run!" << endl;
return 1;
}
}
hThread[0] = CreateThread(NULL, 0, SaleThread1, NULL, 0, NULL);
hThread[1] = CreateThread(NULL, 0, SaleThread2, NULL, 0, NULL);
hThread[2] = CreateThread(NULL, 0, SaleThread3, NULL, 0, NULL);
if (WaitForMultipleObjects(3, hThread, TRUE, INFINITE) != WAIT_OBJECT_0)
{
return 1;
}
CloseHandle(hThread[0]);
CloseHandle(hThread[1]);
CloseHandle(hThread[2]);
CloseHandle(g_hMutex);
return 0;
}
DWORD WINAPI SaleThread1(
LPVOID lpParameter
)
{
while (TRUE)
{
WaitForSingleObject(g_hMutex, INFINITE);
if (tickets > 0)
{
cout << "SaleThread1 sell ticket:" << tickets-- << endl;
}
else
{
break;
}
ReleaseMutex(g_hMutex);
}
return 0;
};
DWORD WINAPI SaleThread2(
LPVOID lpParameter
)
{
while (TRUE)
{
WaitForSingleObject(g_hMutex, INFINITE);
if (tickets > 0)
{
cout << "SaleThread2 sell ticket:" << tickets-- << endl;
}
else
{
break;
}
ReleaseMutex(g_hMutex);
}
return 0;
};
DWORD WINAPI SaleThread3(
LPVOID lpParameter
)
{
while (TRUE)
{
WaitForSingleObject(g_hMutex, INFINITE);
if (tickets > 0)
{
cout << "SaleThread3 sell ticket:" << tickets-- << endl;
}
else
{
break;
}
ReleaseMutex(g_hMutex);
}
return 0;
};