1.创建线程
调用API函数CreateThread()创建线程,函数原型如下:
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
DWORD dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParemeter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);
该函数的作用是用于创建一个线程,并将返回新建线程的句柄。
1.参数lpThreadAttributes是一个指向结构体SECURITY_ATTRIBUTES的指针,表示指定新建线程的安全属性,该参数可以设置为NULL,表示创建线程时使用默认的安全属性;
2.参数dwStackSize指定线程初始化时地址空间的大小,如果这个参数指定为0,那么新创建线程的地址空间大小与调用该函数的线程地址空间大小一样(继承调用线程的大小);
3.参数lpStartAddress将指定该线程的线程函数的地址,当线程创建成功以后,新建线程将调用该线程函数执行某个功能;
4.参数lpParemeter表示将要传递给新建线程的命令行参数,新建线程可以根据该命令参数的不同而执行不同的功能;
5.参数dwCreationFlags用于指定新线程创建后是否立即运行,若取值为0,表示创建成功后立即运行,若取值为CREATE_SUSPENDED,表示创建成功后暂停运行;
6.参数lpThreadId表示新建线程的ID号,用户可以将该参数设置为NULL。
[例]创建线程
#include<Windows.h>
#include<stdio.h>
DWORD WINAPI myfun1(LPVOIDlpParemeter); //声明线程函数
int main(){
HANDLEh; //定义句柄变量
h= CreateThread(NULL, 0, myfun, NULL, 0, NULL); //创建线程
//关闭线程句柄对象,表示不再通过句柄对象对线程进行操作
CloseHandle(h);
return0;
}
DWORD WINAPI myfun(LPVOIDlpParemeter){ //线程执行函数
cout<<”线程执行函数”<<endl;
return0; //正常结束线程函数
}
2.实现线程同步
1.临界区对象
1、使用API函数操作临界区
使用临界区对象前必须对临界区进行初始化。调用函数InitializeCriticalSection()对临界区对象进行初始化,该函数原型如下:
voidInitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
函数作用是为应用程序初始化临界区。
参数lpCriticalSection是指向结构体CRITICAL_SECTION的指针变量。
当用户对临界区进行初始化以后,程序便可以进入该临界区并拥有该临界区对象的所有权,其他程序只能等待进入临界区的程序释放临界区的所有权后,才能进入临界区进行操作。
进入临界区时调用函数EnterCriticalSection()来实现,函数原型如下:
voidEnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
函数作用是使调用该函数的线程程序进入已经初始化的临界区,并拥有该临界区的所有权。如果获得所有权成功,则该函数将返回,调用线程继续执行;否则,该函数将一直等待,造成调用该函数的线程也一直等待,直到获得临界区所有权。
当线程使用完共享资源后,必须离开临界区并释放对该临界区的所有权,一边让其他线程也获得访问该共享资源的机会。函数LeaveCriticalSection()的作用是使已经进入临界区的线程释放对该临界区的所有权,并离开临界区,函数原型如下:
voidLeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
释放临界区的所有权之后,用户应该在程序中调用函数DelectCriticalSection()将该临界区从内存中删除,函数原型如下:
void DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
函数作用是删除程序中已经被初始化的临界区。
[例]
#include<Windows.h>
#include<stdio.h>
DWORD WINAPI myfun1(LPVOIDlpParemeter); //声明线程函数
DWORD WINAPI myfun2(LPVOID lpParemeter);
static int a1 = 0; //定义全局变量并初始化
CRITICAL_SECTION Section; //定义临界区对象
int main(){
HANDLEh1, h2; //定义句柄变量
h1= CreateThread(NULL, 0, myfun1, NULL, 0, NULL); //创建线程1
printf("线程1开始运行!\r\n");
h2= CreateThread(NULL, 0, myfun2, NULL, 0, NULL); //创建线程2
printf("线程2开始运行!\r\n");
//关闭线程句柄对象,表示不再通过句柄对象对线程进行操作
CloseHandle(h1);
CloseHandle(h2);
InitializeCriticalSection(&Section); //初始化临界区对象
Sleep(10000);
printf("正常退出程序请按‘q’\r\n");
if(getchar() == 'q'){
DeleteCriticalSection(&Section); //删除临界区对象
}
else{
return0;
}
}
DWORD WINAPI myfun1(LPVOIDlpParemeter){ //线程执行函数
while(1){
EnterCriticalSection(&Section); //进入临界区
a1++;
if(a1 < 10000){
::Sleep(1000);
printf("线程1正在计数%d\r\n", a1);
LeaveCriticalSection(&Section); //离开临界区
}
else{
LeaveCriticalSection(&Section); //离开临界区
break; //退出
}
}
return0; //正常结束线程函数
}
/*功能同上*/
DWORD WINAPI myfun2(LPVOID lpParemeter){
while(1){
EnterCriticalSection(&Section);
a1++;
if(a1 < 10000){
::Sleep(1000);
printf("线程2正在计数%d\r\n", a1);
LeaveCriticalSection(&Section);
}
else{
LeaveCriticalSection(&Section);
break;
}
}
return0;
}
2、使用CCriticalSection类操作临界区
CCriticlaSection类是MFC中所定义的临界区类,作用和临界区相关API函数实现的功能一样。
为了方便线程访问CCriticalSection类对象,必须将该对象定义为全局变量,格式如下:
CCriticalSection m_Sec;
调用该类中成员函数Lock()对临界区进行锁定,函数原型如下:
BOOL Lock();
作用是程序进入临界区执行相关功能并获得该临界区的所有权,如果函数调用成功,则返回true,否则,函数返回false。
如果不再使用临界区,可以调用成员函数Unlock()离开临界区并释放其所有权,函数原型如下:
virtual BOOL Unlock(); //该函数调用成功返回true,否则返回false。
[例]
#include"stdafx.h"
#include<Windows.h>
#include<stdio.h>
#include"afxmt.h"
DWORD WINAPI myfun1(LPVOID lpParemeter); //声明线程函数
DWORD WINAPI myfun2(LPVOID lpParemeter);
CCriticalSection m_Sec; //定义全局变量m_Sec
int a = 0; //定义全局变量a
int main(){
HANDLEh1, h2; //定义句柄变量
h1= CreateThread(NULL, 0, myfun1, NULL, 0, NULL); //创建线程1
printf("线程1开始运行!\r\n");
h2= CreateThread(NULL, 0, myfun2, NULL, 0, NULL); //创建线程2
printf("线程2开始运行!\r\n");
//关闭线程句柄对象,表示不再通过句柄对象对线程进行操作,不影响线程执行
CloseHandle(h1);
CloseHandle(h2);
::Sleep(10000);
return0;
}
DWORD WINAPI myfun1(LPVOID lpParemeter){ //线程执行函数
m_Sec.Lock(); //锁定临界区
a++;
printf("%d",a);
m_Sec.Unlock(); //对临界区去进行解锁
return0; //正常结束线程函数
}
/*功能同上*/
DWORD WINAPI myfun2(LPVOID lpParemeter){
m_Sec.Lock();
a++;
printf("%d",a);
m_Sec.Unlock();
return0; //正常结束线程函数
}
2.事件对象
事件对象是指用户在程序中使用内核对象的有无信号状态实现线程的同步。
1.使用API函数操作事件对象
使用对象必须首先创建事件对象。在API函数中,可以使用函数CreateEvent()创建并返回事件对象,函数原型如下:
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes,
BOOL bManualReset,
BOOL bInitialState,
LPCTSTR lpName
);
如果函数调用成功,返回新创建的事件对象。
1.参数lpEventAttributes是结构体SECURITY_ATTRIBUTES的指针,表示新创建的事件对象的安全属性,如果该参数为NULL,则表示默认安全属性;
2.参数bManualReset表示所创建的事件对象是人工重置还是自动重置,如果参数为true,则表示该对象为人工重置对象(即当调用线程获得其所有权后,用户要显式地调用函数ResetEvent()将事件对象设置为无信号状态),如果为false,则表示自动重置对象(常用)(系统自动调用ResetEvent()函数将事件对象设置为无信号状态);
3.参数bInitialState表示事件对象的初始状态,如果该参数为true,则表示该事件对象初始时为有信号状态,否则,表示初始时为无信号状态;
4.参数lpName表示事件对象的名称,如果该参数为NULL,则表示程序创建的是一个匿名的事件对象。
如果初始状态为无信号状态,则需要手动调用函数SetEvent()将其设置为有信号状态。函数原型如下:
BOOL SetEvent(HANDLE hEvent);
如果函数调用成功,则返回true,否则,将返回false。
参数hEvent表示将设置的事件对象句柄。
注:与其功能相反,函数ResetEvent()则将指定的事件对象设置为无信号状态,参数及意义与函数SetEvent()相同。
线程也可以通过调用函数WaitForSingleObject()主动请求事件对象,函数原型如下:
DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds);
参数hHandle表示函数所等待的对象的句柄;
参数dwMilliseconds表示该函数将在事件对象上的等待时间,如果该参数为INFINITE,则该函数将永远等待;
返回值若为WAIT_TIMEOUT,表示用户指定的等待时间已过;
返回值若为WAIT_OBJECT_0,表示线程所请求的对象为有信号状态。
[例]
#include<Windows.h>
#include<stdio.h>
DWORD WINAPI myfun1(LPVOID lpParemeter);
DWORD WINAPI myfun2(LPVOID lpParemeter);
HANDLE hevent;
int a = 0;
int main(){
HANDLEh1, h2; //定义句柄变量
hevent= CreateEvent(NULL, FALSE, false, NULL);
SetEvent(hevent);
h1= CreateThread(NULL, 0, myfun1, NULL, 0, NULL); //创建线程1
printf("线程1开始运行!\r\n");
h2= CreateThread(NULL, 0, myfun2, NULL, 0, NULL); //创建线程2
printf("线程2开始运行!\r\n");
//关闭线程句柄对象,表示不再通过句柄对象对线程进行操作
CloseHandle(h1);
CloseHandle(h2);
Sleep(10000);
return0;
}
DWORD WINAPI myfun1(LPVOIDlpParemeter){ //线程执行函数
while(1){
::WaitForSingleObject(hevent,INFINITE); //主动请求事件对象
::ResetEvent(hevent); //设置事件对象为无信号状态
if(a < 10000){
a++;
::Sleep(1000);
printf("线程1:%d\r\n", a);
::SetEvent(hevent); //设置事件对象为有信号状态
}
else{
::SetEvent(hevent);
break;
}
}
return0; //正常结束线程函数
}
/*功能如上*/
DWORD WINAPI myfun2(LPVOIDlpParemeter){ //线程执行函数
while(1){
::WaitForSingleObject(hevent,INFINITE); //主动请求事件对象,等待
::ResetEvent(hevent); //占用事件对象,将其设置为无信号状态
if(a < 10000){
a++;
::Sleep(1000);
printf("线程2:%d\r\n", a);
::SetEvent(hevent); //释放事件对象,将其设置为有信号状态
}
else{
::SetEvent(hevent);
break;
}
}
return0; //正常结束线程函数
}
2.使用CEvent类实现线程同步
CEvent类是MFC中支持事件对象编程的类。
调用该类构造函数创建对象,构造函数原型如下:
CEvent(
BOOL bInitiallyOwn = false,
BOOL bManualReset = false,
LPCTSTR lpszName = NULL,
LPSECURITY_ATTRIBUTES lpsaAttribute = NULL
);
1.参数bInitiallyOwn表示事件的初始化状态,如果该参数为true,则表示该事件对象为有信号状态,否则为无信号状态(默认);
2.参数bManualReset表示该事件对象是人工重置还是自动重置对象,true表示人工重置;
3.参数lpszName表示用户为该事件对象的命名,默认为NULL;
4.参数lpsaAttribute表示该事件对象的安全属性,NULL表示默认安全属性。
事件对象的信号状态转换成员函数:
BOOL SetEvent(); //设置事件对象为有信号状态
BOOL ResetEvent(); //设置事件对象为无信号状态
函数调用成功,则返回true,否则,将返回false。
3.互斥对象
同临界区对象和事件对象的作用一样,均用于实现线程同步。但是,互斥对象还可以在进程之间使用。
1.使用API函数操作互斥对象
调用API 函数CreateMutex()创建并返回互斥对象,函数原型如下:
HANDLE CreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes,
BOOL bInitialOwner,
LPCTSTR lpName
);
如果函数调用成功,将返回新创建的互斥对象句柄,否则,将返回HULL。
1.参数lpMutexAttributes指定新创建互斥对象的安全属性,如果该参数为NULL,表示默认的安全属性;
2.参数bInitialOwner表示是否拥有该互斥对象,如果为true,表示创建该互斥对象的线程拥有其所有权,如果为false,表示创建该互斥对象的线程不能拥有其所有权;
3.参数lpName表示互斥对象的名称,如果为NULL,表示程序创建的是匿名对象,如果用户为该参数指定值,则在程序中可以调用函数OpenMutex()打开一个命名的互斥对象。
线程使用完该互斥对象以后,调用函数ReleaseMutex()释放对该互斥对象的所有权,即让互斥对象处于有信号状态,函数原型如下:
BOOL ReleaseMutex(HANDLE hMutex);
如果函数调用成功,返回true,否则返回false。
参数hMutex表示将释放的互斥对象句柄。
也可以调用WaitForSingleObject()对该对象进行请求,当互斥对象无信号时,函数等待。
[例]
#include<Windows.h> //包含头文件
#include<stdio.h>
DWORD WINAPI myfun1(LPVOIDlpParameter); //声明线程函数
DWORD WINAPI myfun2(LPVOID lpParameter);
HANDLE hmutex;
int a = 0;
int main(){
hmutex= ::CreateMutex(NULL, FALSE, NULL);//创建互斥对象并返回其句柄
HANDLEh1, h2;
h1= CreateThread(NULL, 0, myfun1, NULL, 0, NULL); //创建线程1
printf("线程1开始运行!\r\n");
h2= CreateThread(NULL, 0, myfun2, NULL, 0, NULL); //创建线程2
printf("线程2开始运行!\r\n");
CloseHandle(h1); //关闭句柄对象
CloseHandle(h2);
Sleep(10000); //程序睡眠10秒
return0;
}
DWORD WINAPI myfun1(LPVOIDlpParameter){ //定义线程函数
while(1){
WaitForSingleObject(hmutex,INFINITE); //请求互斥对象
if(a < 10000){
a++;
Sleep(1000); //线程睡眠1秒
printf("线程1:%d\r\n", a);
ReleaseMutex(hmutex); //通过句柄释放互斥对象
}
else{
ReleaseMutex(hmutex);
break;
}
}
return0;
}
/*功能同上*/
DWORD WINAPI myfun2(LPVOID lpParameter){
while(1){
WaitForSingleObject(hmutex,INFINITE);
if(a < 10000){
a++;
Sleep(1000);
printf("线程2:%d\r\n", a);
ReleaseMutex(hmutex);
}
else{
ReleaseMutex(hmutex);
break;
}
}
return0;
}
根据https://bbs.csdn.net/topics/390849625帖子所说
互斥对象包含:1个使用数量(多少个线程在调用该对象)、1个线程ID(互斥对象维护的线程的ID)、1个计数器(计数器表示当前线程调用该互斥对象的次数)
Mutex递归计数器
HANDLE CreateMutex(
LPSECURITY_ATTRIBUTESlpMutexAttributes, //指向安全属性的指针,可为NULL,表示默认安全性
BOOLbInitialOwner, //初始化互斥对象的所有者,是否占有该互斥量,TRUE:占有,FALSE:不占有。(为TRUE,则记录线程ID,将递归计数器设置为1,互斥量处于未触发/无信号/未通知状态,为创建线程所拥有)(为FALSE,则线程ID为NULL,将递归计数器设置为0,互斥量处于触发/有信号/已通知状态,不为任何线程所占用)
LPCTSTRlpName //指向互斥对象名的指针,设置互斥对象的名字
);
hMutex=CreateMutex(NULL,TRUE,NULL);//当前线程调用该互斥对象次数为1,递归计数器设置为1
WaitForSingleObject(hMutex,INFINITE);//当前线程调用该互斥对象次数为2,递归计数器+1,为2
ReleaseMutex(hMutex);//释放占用互斥对象的线程的拥有权,递归计数器-1,递归计数器值为1
ReleaseMutex(hMutex);//释放占用互斥对象的线程的拥有权,递归计数器-1,递归计数器值为0(递归计数器值为0,互斥对象处于触发/有信号/已通知状态,可以在此为其他线程所占)
2.使用CMutex类
CMutex类是MFC中的互斥对象类。该类是由CSyneObject类派生而来,所以使用CMutex类时可以调用其父类CSyncObject中的成员函数实现指定功能。
创建CMutex类对象是通过其构造函数实现的,构造函数原型如下:
CMutex(
BOOL bInitiallyOwn = FALSE,
LPCTSTR lpszName = NULL,
LPSECURITY_ATTRIBUTES lpsaAttribute = NULL
);
函数作用是构造CMutex类的实例对象。
1.参数bInitiallyOwn表示调用该线程是否拥有所创建的互斥对象,如果为true,则表示创建该对象的线程拥有其所有权,否则,不能拥有所有权;
2.参数lpName表示互斥对象的名称,如果为NULL,则表示程序创建的是匿名对象;
3.参数lpMutexAttributes指定新创建互斥对象的安全属性,如果为NULL,则表示该对象拥有默认的安全属性。
调用Lock()和Unlock()对该互斥对象所保护的区域进行锁定和解锁,控制其他线程对保护区域的访问权限,函数原型如下:
virtual BOOL Lock(DWORD dwTimeout = INFINITE);
virtual BOOL Unlock(LONG lCount, LPLONG lpPrevCount =NULL);
1.Lock()函数的作用是锁定保护区域的数据,将互斥对象设置为无信号,调用成功返回true,否则返回false。
参数dwTimeout表示等待该互斥对象变为有信号状态的时间,如果为INFINITE,则表示该函数一直等待,直到互斥对象变为有信号状态。
2.Unlock()函数的作用是解除对保护区域数据的锁定,并将互斥对象设置为有信号状态,函数调用成功返回true,否则返回false。
参数lCount为默认参数,用户在使用时可以不为其指定值;
参数lpPrevCount也是默认参数,默认为NULL。
[例]未实现,VS2013编译器无法识别CMutex