多线程的基础知识

1. 线程由两部分组成:

<1> 线程内核对象,操作系统用它来对线程实施管理。内核对象也是系统用来存放线程统计信息的地地方

<2>线程堆栈:它用于维护线程在执行代码时需要的所有函数参数和局部变量.线程总是在某个进程环境中创建,系统从进程的地址空间中分配内存,供线程的栈使用.

线程运行:

操作系统为每一个运行线程安排一定的CPU时间—时间片,系统通过一种循环的方式为线程提供时间片,线程在自己的时间内运行,因时间片相当短,因此给用户的感觉就是好像多个线程是同时运行一样.

进程由两部分组成:

<1> 一个是操作系统用来管理进程的内核对象,也是系统用来存放关于进程的统计信息的地方。

<2> 另一个是地址空间,包含所有可执行模块和DLL模块的代码和数据。还包含动态内存分配的空间。如线程堆栈和堆分配空间。


2. 多线程的创建


2.1 Win32 API:

_beginthreadex 代替CreateThread, 完成CRT函数的一些初始化工作,避免内存泄漏。

线程退出: _endthreadex

结束时退出线程对象:closehandle
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的封装

一次性创建并启动线程:
CWinThread* AfxBeginThread( 线程函数,this );
m_pThread[i] = AfxBeginThread((AFX_THREADPROC)WorkThread, this, 0, 0, CREATE_SUSPENDED);

两步法创建:

首先创建CWinThread类的一个对象,然后调用该对象的成员函数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);
其中 AFX_THREADPROC为一个宏,其定义如下:
typedef   UINT ( AFX_CDECL  * AFX_THREADPROC )( LPVOID );
从以上语句,可以得到工作线程的线程函数的形式为:
UINT ThreadFunc(LPVOID pParm);

(2)UIThread创建
CWinThread* AfxBeginThread(CRuntimeClass* pThreadClass,

int nPriority=THREAD_PRIORITY_NORMAL,

UINT nStackSize=0,

DWORD dwCreateFlags=0,

LPSECURITY_ATTRIBUTES lpSecurityAttrs=NULL);

pThreadClass 是指向 CWinThread 的一个导出类的运行时类对象的指针,该导出类定义了被创建的用户界面线程的启动、退出等;
UI线程重载以下2个函数:
virtual  BOOL  CWinThread::InitInstance(); //初始化
virtual  int  CWinThread::ExitInstance(); //清理工作
CWinThread类的数据成员及常用函数进行简要说明:
m_hThread:当前线程的句柄 ;m_nThreadID:当前线程的ID;m_pMainWnd:执行应用程序主窗口的指针

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_OBJECTS64)  

*lpHandles 句柄数组的指针。lpHandles为指定对象句柄组合中的第一个元素 HANDLE类型可以为(EventMutexProcessThreadSemaphore)数组  

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

bWaitAllFALSE,那么返回结果相似,只是可能还会返回相对于WAIT_ABANDONED_0WAIT_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;
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值