其实C++语言本身并没有提供多线程机制(当然目前C++ 11新特性中,已经可以使用std::thread来创建线程了,因为还没有系统地了解过,所以这里不提了。),但Windows系统为我们提供了相关API,我们可以使用他们来进行多线程编程。
1、线程的创建及销毁
(1)线程创建
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,//initialstacksize:
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
)
创建一个线程,函数调用成功,返回线程句柄,否则返回NULL。
lpThreadAttributes:指向SECURITY_ATTRIBUTES结构的指针,决定返回的句柄是否可被子进程继承,如果为NULL则表示返回的句柄不能被子进程继承。
dwStackSize:设置初始栈的大小,以字节为单位,如果为0,那么默认将使用与调用该函数的线程相同的栈空间大小。
lpStartAddress:指向线程函数的指针,函数名称没有限制,但是必须以下列形式声明:DWORD WINAPI 函数名 (LPVOID lpParam) ,格式不正确将无法调用成功。
lpParameter:向线程函数传递的参数,是一个指向结构的指针,不需传递参数时,为NULL。
dwCreationFlags:控制线程创建的标志,可取值如下:
(1)CREATE_SUSPENDED(0x00000004):创建一个挂起的线程(就绪状态),直到线程被唤醒时才调用
(2)0: 表示创建后立即激活。
(3)STACK_SIZE_PARAM_IS_A_RESERVATION(0x00010000):dwStackSize参数指定初始的保留堆栈的大小,如果STACK_SIZE_PARAM_IS_A_RESERVATION标志未指定,dwStackSize将会设为系统预留的值
lpThreadId:保存新线程的id
(2)关闭线程
BOOL TerminateThread(HANDLE hThread,DWORD dwExitCode);
VOID ExitThread(DWORD dwExitCode);
BOOL WINAPI CloseHandle(HANDLE hObject);
TerminateThread函数只能由外部进行调用,关闭某线程,ExitThread函数线程自身主动调用,结束线程,以上两个函数一般配合CloseHandle函数一起使用。如果函数执行成功则返回true(非0),如果失败则返回false(0)
hThread: 欲结束线程的句柄
dwExitCode:退出码,一般置为NULL
HANDLE : 线程句柄
2、线程同步机制
(1)互斥量
/*互斥量创建*/
HANDLE WINAPI CreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes,
BOOL bInitialOwner,
LPCTSTR lpName
);
。
MutexAttributes:也是表示安全的结构,与CreateThread中的lpThreadAttributes功能相同,表示决定返回的句柄是否可被子进程继承,如果为NULL则表示返回的句柄不能被子进程继承。
bInitialOwner:表示创建Mutex时的当前线程是否拥有Mutex的所有权,若为TRUE则指定为当前的创建线程为Mutex对象的所有者,其它线程访问需要先ReleaseMutex
lpName:Mutex的名称
/*等待互斥量*/
DWORD WINAPI WaitForSingleObject(
HANDLE hHandle,
DWORD dwMilliseconds
);
WaitForSingleObject函数用来等待一个指定的对象(互斥量、临界区、事件、信号量),直到该对象处于非占用的状态(如Mutex对象被释放)或超出设定的时间间隔。除此之外,还有一个与它类似的函数WaitForMultipleObjects,它的作用是等待一个或所有指定的对象,直到所有的对象处于非占用的状态,或超出设定的时间间隔。
hHandle:要等待的指定对象的句柄,及互斥量。
dwMilliseconds:超时的间隔,以毫秒为单位;如果dwMilliseconds为非0,则等待直到dwMilliseconds时间间隔用完或对象变为非占用的状态,如果dwMilliseconds 为INFINITE则表示无限等待,直到等待的对象处于非占用的状态。
BOOL WINAPI ReleaseMutex(HANDLE hMutex);
函数用来释放所拥有的互斥量锁对象,hMutex为释放的互斥量句柄。
#include
#include
using namespace std;
HANDLE hMutex; /*定义全局互斥量*/
DWORD WINAPI ThreadProc2(LPVOID p)
{
int i = 0;
while(1)
{
WaitForSingleObject(hMutex,INFINITE);/*无限时间等待互斥量*/
cout<<"线程2正在运行"</*释放互斥量*/
Sleep(1000);/*睡眠1s,必不可少,将cpu交给其他线程*/
}
return 0;
}
DWORD WINAPI ThreadProc1(LPVOID p)
{
int i = 0;
while(i < 1)
{
WaitForSingleObject(hMutex,INFINITE); /*无限时间等待互斥量*/
cout<<"线程1正在运行"</*释放互斥量*/
Sleep(1000);/*睡眠1s,必不可少,将cpu交给其他线程*/
}
return 0;
}
int main(int argc, _TCHAR* argv[])
{
HANDLE HThread;
HANDLE HThread1;
hMutex = CreateMutex(NULL,false,false);/*初始化互斥量属性*/
HThread = CreateThread(NULL,0,ThreadProc1,NULL,0,NULL);/*创建线程1*/
HThread1 = CreateThread(NULL,0,ThreadProc2,NULL,0,NULL);/*创建线程2*/
cin.get() ;
return 0;
}
(2)临界区
CRITICAL_SECTION cs; //定义临界区对象
InitializeCriticalSection(&cs); //初始化临界区
EnterCriticalSection(&cs); //进入临界区
LeaveCriticalSection(&cs); //离开临界区
DeleteCriticalSection(&cs); //删除临界区
(3)事件
一个Event被创建以后,可以用OpenEvent()API来获得它的Handle,用CloseHandle() 来关闭它,用SetEvent()来设置它使其有信号,用ResetEvent() 来使其无信号,用WaitForSingleObject()或WaitForMultipleObjects()来等待 其变为有信号。
/*创建事件对象*/
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTESlpEventAttributes,
BOOLbManualReset,
BOOLbInitialState,
LPCTSTRlpName
);
lpEventAttributes[In]: 一个指向SECURITY_ATTRIBUTES结构的指针,确定返回的句柄是否可被子进程继承。如果lpEventAttributes是NULL,此句柄不能被继承
bManualReset[In]: 指定将事件对象创建成手动复原还是自动复原。如果是TRUE,那么必须用ResetEvent函数来手工将事件的状态复原到无信号状态。如果设置为FALSE,当事件被一个等待线程释放以后,系统将会自动将事件状态复原为无信号状态。
bInitialState[In]: 指定事件对象的初始状态。如果为TRUE,初始状态为有信号状态;否则为无信号状态。
lpName[In]:指定事件的对象的名称,是一个以0结束的字符串指针。
返回值:如果函数调用成功,函数返回事件对象的句柄。如果对于命名的对象,在函数调用前已经被创建,函数将返回存在的事件对象的句柄,如果函数失败,函数返回值为NULL
/*等待事件对象,同WaitForSingleObject */
DWORD WaitForMultipleObjects(
DWORD nCount,
CONST HANDLE *lpHandles,
BOOL fWaitAll,
DWORD dwMilliseconds
);
nCount: 句柄的数量 最大值为MAXIMUM_WAIT_OBJECTS(64)
HANDLE: 句柄数组的指针。 HANDLE 类型可以为(Event,Mutex,Process,Thread,Semaphore )数组 。
BOOL bWaitAll: 等待的类型,如果为TRUE 则等待所有信号量有效在往下执行,FALSE 当有其中一个信号量有效时就向下执行。
DWORD dwMilliseconds: 超时时间 超时后向执行。 如果为WSA_INFINITE 永不超时。如果没有信号量就会在这死等。
/*设置事件Handle为有信号状态,即其他线程可以获取该事件*/
BOOL SetEvent(HANDLE Handle);
/*设置事件Handle为无信号状态,即其他线程无法获取该事件*/
BOOL ResetEvent(HANDLE Handle)
(3)信号量
//头文件
#include <windows.h>
//创建信号量API
HANDLE WINAPI CreateSemaphore(
_In_opt_ LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,//指向SECURITY_ATTRIBUTES的指针;
_In_ LONG lInitialCount, //信号量对象的初始值;
_In_ LONG lMaximumCount, //信号量对象的最大值,这个值必须大于0;
_In_opt_ LPCTSTR lpName //信号量对象的名称;
);
//等待信号量API
DWORD WINAPI WaitForSingleObject(
_In_ HANDLE hHandle, //信号量对象句柄
_In_ DWORD dwMilliseconds //等待信号量时间,INFINET代表永久等待;
);
返回值:
WAIT_ABANDONED(0x00000080L) 表示拥有信号量的线程再终止前未释放该信号量;
WAIT_OBJECT_0(0x00000000L) 表示等到了信号量;
WAIT_TIMEOUT(0x00000102L) 表示等待超时;
WAIT_FAILED((DWORD)0xFFFFFFFF) 表示该函数执行失败,用GetLastError()得到错误码;
//释放信号量句柄
BOOL WINAPI ReleaseSemaphore(
_In_ HANDLE hSemaphore, //信号量对象句柄;
_In_ LONG lReleaseCount, //信号量释放的值,必须大于0;
_Out_opt_ LPLONG lpPreviousCount //前一次信号量值的指针,不需要可置为空;
);
返回值:成功返回非0;
3、线程间通信
/*发送消息*/
BOOL PostThreadMessage(DWORD idThread,UINT Msg,WPARAM wParam,LPARAM lParam);
idThread:目标线程ID,可通过GetCurrentThreadId(),获取当前线程的ID
Msg:要发送的消息类型
wParam、lParam:消息内容
BOOL GetMessage(LPMSG lpMsg,HWND hWnd,UINT wMsgFilterMin,UINT wMsgFilterMax);
lpMsg:读取到的消息内容
hWnd:获取消息的窗口句柄,无则置为NULL
wMsgFilterMin:消息的最低整数值
wMsgFilterMax:消息的最高整数值
#include "stdafx.h"
#include
#include
#include
#include
using namespace std;
#define UM_MSG1 WM_USER+1
#define UM_MSG2 WM_USER+2
DWORD WINAPI Thread1(LPVOID para)
{
DWORD dwThreadId = *(DWORD *)para;
DWORD i=0;
char strTmp[100];
while(TRUE)
{
Sleep(1700);
sprintf(strTmp,"Hello %d ",i++);
PostThreadMessage(dwThreadId,UM_MSG1,(WPARAM)strTmp,(LPARAM)NULL);
}
return 0;
}
DWORD WINAPI Thread2(LPVOID para)
{
char strTmp[100];
DWORD dwThreadId = *(DWORD *)para;
DWORD i=0;
while(TRUE)
{
Sleep(3000);
sprintf(strTmp,"World %d \n",i++);
PostThreadMessage(dwThreadId,UM_MSG2,(WPARAM)strTmp,(LPARAM)NULL);
}
return 0;
}
int main()
{
DWORD dwValue =GetCurrentThreadId();
cout<<"GetCurrentThreadId "<0,&Thread1,&dwValue,0,NULL);
HANDLE hThread2 = CreateThread(NULL,0,&Thread2,&dwValue,0,NULL);
CloseHandle(hThread1);
CloseHandle(hThread2);
MSG msg;
while(GetMessage(&msg,NULL,0,0))
{
switch(msg.message)
{
case UM_MSG1:
case UM_MSG2:
printf("msg:%d: %s\n",msg.message,msg.wParam);
break;
default:
printf("Unknown msg:0x%x\n",msg.message);
break;
}
Sleep(1);
}
return 0;
}
总结:临界区是用户模式的线程同步,而事件、互斥量、信号量则是内核模式的线程同步,因此临界区相对于其他三种同步方式,减少用户态和内核态之间转换的过程,因此运行速度更快,但相对的适应性也差些。