进程:(一般是一个应用程序)活动性不强
通常定义为一个正在运行的程序实例
组成:
- 操作系统用来管理进程的内核对象
- 地址空间:所有的可执行模块,dll模块(动态库),所需要的数据(内存四区)
- 进程包含线程
线程:是进程的一部分,总是在某个进程的环境中吧诶创建;
是进程的某一个执行序列(函数),有进程,必然有线程。
组成:
- 操作系统对线程实施管理的内核对象
- 线程堆栈
如果需要完成多个操作,尽量通过多线程来处理。
量程:window系统为操作多个应用程序所准备的
操作系统为每一个线程安排了一定的cpu时间,它通过一种循环的方式为线程提供了时间片。
进程Process
创建进程
CreateProcess();
BOOL CreateProcess(LPCTSTR lpApplicationName, //指向一个NULL结尾的、用来指定可执行模块的字符串
LPTSTR lpCommandLine,//指向一个以NULL结尾的字符串,该字符串指定要执行的命令行。
LPSECURITY_ATTRIBUTES lpProcessAttributes,//指向一个SECURITY_ATTRIBUTES结构体,这个结构体决定是否返回的句柄可以被子进程继承
LPSECURITY_ATTRIBUTES lpThreadAttributes,//同lpProcessAttribute,不过这个参数决定的是线程是否被继承.通常置为NULL
BOOL bInheritHandles,//指示新进程是否从调用进程处继承了句柄
DWORD dwCreationFlags,//指定附加的、用来控制优先类和进程的创建的标志
LPVOID lpEnvironment,//指向一个新进程的环境块
LPCTSTR lpCurrentDirectory,//指向一个以NULL结尾的字符串,这个字符串用来指定子进程的工作路径
LPSTARTUPINFO lpStartupInfo,//指向一个用于决定新进程的主窗体如何显示的STARTUPINFO结构体
LPPROCESS_INFORMATIONlpProcessInformation);//指向一个用来接收新进程的识别信息的PROCESS_INFORMATION结构体
参数说明
lpApplicationName
指向一个NULL结尾的、用来指定可执行模块的字符串。
这个字符串可以是可执行模块的绝对路径,也可以是相对路径,在后一种情况下,函数使用当前驱动器和目录建立可执行模块的路径。
这个参数可以被设为NULL,在这种情况下,可执行模块的名字必须处于 lpCommandLine 参数最前面并由空格符与后面的字符分开。
lpCommandLine
指向一个以NULL结尾的字符串,该字符串指定要执行的命令行。
这个参数可以为空,那么函数将使用lpApplicationName参数指定的字符串当做要运行的程序的命令行。
如果lpApplicationName和lpCommandLine参数都不为空,那么lpApplicationName参数指定将要被运行的模块,lpCommandLine参数指定将被运行的模块的命令行。新运行的进程可以使用GetCommandLine函数获得整个命令行。C语言程序可以使用argc和argv参数。
lpProcessAttributes
指向一个SECURITY_ATTRIBUTES结构体,这个结构体决定是否返回的句柄可以被子进程继承。如果lpProcessAttributes参数为空(NULL),那么句柄不能被继承。
在Windows NT中:SECURITY_ATTRIBUTES结构的lpSecurityDescriptor成员指定了新进程的安全描述符,如果参数为空,新进程使用默认的安全描述符。
lpThreadAttributes
同lpProcessAttribute,不过这个参数决定的是线程是否被继承.通常置为NULL.
bInheritHandles
指示新进程是否从调用进程处继承了句柄。
如果参数的值为真,调用进程中的每一个可继承的打开句柄都将被子进程继承。被继承的句柄与原进程拥有完全相同的值和访问权限。
dwCreationFlags
指定附加的、用来控制优先类和进程的创建的标志。以下的创建标志可以以除下面列出的方式外的任何方式组合后指定。
- 对于CreateProcess函数,默认的行为是为新进程继承调用者的错误模式。设置这个CREATE_DEFAULT_ERROR_MODE标志以改变默认的处理方式。
- 如果你使用这个DEBUG_PROCESS标志创建进程,只有调用进程(调用CreateProcess函数的进程)可以调用WaitForDebugEvent函数。
|值|描述|
|—|---|
|CREATE_DEFAULT_ERROR_MODE|含义:新的进程不继承调用进程的错误模式。CreateProcess函数赋予新进程当前的默认错误模式作为替代。应用程序可以调用SetErrorMode函数设置当前的默认错误模式。|
|CREATE_NEW_CONSOLE|含义:新的进程将使用一个新的控制台,而不是继承父进程的控制台。这个标志不能与DETACHED_PROCESS标志一起使用。|
|CREATE_NEW_PROCESS_GROUP|含义:新进程将是一个进程树的根进程。进程树中的全部进程都是根进程的子进程。新进程树的用户标识符与这个进程的标识符是相同的,由lpProcessInformation参数返回。进程树经常使用GenerateConsoleCtrlEvent函数允许发送CTRL+C或CTRL+BREAK信号到一组控制台进程。|
|CREATE_SEPARATE_WOW_VDM|如果被设置,新进程将会在一个私有的虚拟DOS机(VDM)中运行。另外,默认情况下所有的16位Windows应用程序都会在同一个共享的VDM中以线程的方式运行。单独运行一个16位程序的优点是一个应用程序的崩溃只会结束这一个VDM的运行;其他那些在不同VDM中运行的程序会继续正常的运行。同样的,在不同VDM中运行的16位Windows应用程序拥有不同的输入队列,这意味着如果一个程序暂时失去响应,在独立的VDM中的应用程序能够继续获得输入。|
|CREATE_SHARED_WOW_VDM|如果WIN.INI中的Windows段的DefaultSeparateVDM选项被设置为真,这个标识使得CreateProcess函数越过这个选项并在共享的虚拟DOS机中运行新进程。|
|CREATE_SUSPENDED|含义:新进程的主线程会以暂停的状态被创建,直到调用ResumeThread函数被调用时才运行。|
|CREATE_UNICODE_ENVIRONMENT|含义:如果被设置,由lpEnvironment参数指定的环境块使用Unicode字符,如果为空,环境块使用ANSI字符。|
|DEBUG_PROCESS|含义:如果这个标志被设置,调用进程将被当做一个调试程序,并且新进程会被当做被调试的进程。系统把被调试程序发生的所有调试事件通知给调试器。|
|DEBUG_ONLY_THIS_PROCESS|含义:如果此标志没有被设置且调用进程正在被调试,新进程将成为调试调用进程的调试器的另一个调试对象。如果调用进程没有被调试,有关调试的行为就不会产生。|
|DETACHED_PROCESS|含义:对于控制台进程,新进程没有访问父进程控制台的权限。新进程可以通过AllocConsole函数自己创建一个新的控制台。这个标志不可以与CREATE_NEW_CONSOLE标志一起使用。|
|CREATE_NO_WINDOW|含义:系统不为新进程创建CUI窗口,使用该标志可以创建不含窗口的CUI程序。|
dwCreationFlags参数
还用来控制新进程的优先类,优先类用来决定此进程的线程调度的优先级。如果下面的优先级类标志都没有被指定,那么默认的优先类是NORMAL_PRIORITY_CLASS,除非被创建的进程是IDLE_PRIORITY_CLASS。在这种情况下子进程的默认优先类是IDLE_PRIORITY_CLASS。
可以选择下面的标志中的一个:
值 | 描述 |
---|---|
HIGH_PRIORITY_CLASS | 指示这个进程将执行时间临界的任务,所以它必须被立即运行以保证正确。这个优先级的程序优先于正常优先级或空闲优先级的程序。一个例子是Windows任务列表,为了保证当用户调用时可以立刻响应,放弃了对系统负荷的考虑。确保在使用高优先级时应该足够谨慎,因为一个高优先级的CPU关联应用程序可以占用几乎全部的CPU可用时间。 |
IDLE_PRIORITY_CLASS | 指示这个进程的线程只有在系统空闲时才会运行并且可以被任何高优先级的任务打断。例如屏幕保护程序。空闲优先级会被子进程继承。 |
NORMAL_PRIORITY_CLASS | 指示这个进程没有特殊的任务调度要求。 |
REALTIME_PRIORITY_CLASS | 指示这个进程拥有可用的最高优先级。一个拥有实时优先级的进程的线程可以打断所有其他进程线程的执行,包括正在执行重要任务的系统进程。例如,一个执行时间稍长一点的实时进程可能导致磁盘缓存不足或鼠标反映迟钝。 |
lpEnvironment
指向一个新进程的环境块。如果此参数为空,新进程使用调用进程的环境。
一个环境块存在于一个由以NULL结尾的字符串组成的块中,这个块也是以NULL结尾的。每个字符串都是name=value的形式。
因为相等标志被当做分隔符,所以它不能被环境变量当做变量名。
与其使用应用程序提供的环境块,不如直接把这个参数设为空,系统驱动器上的当前目录信息不会被自动传递给新创建的进程。对于这个情况的探讨和如何处理,请参见注释一节。
环境块可以包含Unicode或ANSI字符。如果lpEnvironment指向的环境块包含Unicode字符,那么dwCreationFlags字段的CREATE_UNICODE_ENⅥRONMENT标志将被设置。如果块包含ANSI字符,该标志将被清空。
请注意一个ANSI环境块是由两个零字节结束的:一个是字符串的结尾,另一个用来结束这个快。一个Unicode环境块是由四个零字节结束的:两个代表字符串结束,另两个用来结束块。
lpCurrentDirectory
指向一个以NULL结尾的字符串,这个字符串用来指定子进程的工作路径。这个字符串必须是一个包含驱动器名的绝对路径。如果这个参数为空,新进程将使用与调用进程相同的驱动器和目录。这个选项是一个需要启动应用程序并指定它们的驱动器和工作目录的外壳程序的主要条件。
lpStartupInfo
指向一个用于决定新进程的主窗体如何显示的STARTUPINFO结构体。
//该结构用于指定新进程的主窗口特性
typedef struct _STARTUPINFO {
DWORD cb;
LPTSTR lpReserved;
LPTSTR lpDesktop;
LPTSTR lpTitle;
DWORD dwX;
DWORD dwY;
DWORD dwXSize;
DWORD dwYSize;
DWORD dwXCountChars;
DWORD dwYCountChars;
DWORD dwFillAttribute;
DWORD dwFlags;
WORD wShowWindow;
WORD cbReserved2;
LPBYTE lpReserved2;
HANDLE hStdInput;
HANDLE hStdOutput;
HANDLE hStdError;
} STARTUPINFO, *LPSTARTUPINFO;
lpProcessInformation
指向一个用来接收新进程的识别信息的PROCESS_INFORMATION结构体。
返回值
如果函数执行成功,返回非零值。
如果函数执行失败,返回零,可以使用GetLastError函数获得错误的附加信息。
代码示例
//新进程的窗口特性
STARTUPINFO si;
//保存新进程的识别信息
PROCESS_INFORMATION pi;
//创建新进程
CreateProcess(_T("C:/Windows/notepad.exe"), //打开的程序名字
nullptr, //命令行参数
nullptr, //是否被子进程所继承
nullptr, //是否被子线程所继承
false, //新创建的进程是否从调用处继承
0, //创建标识
nullptr, //新进程的环境块
nullptr, //子进程的工作路径
&si, //
&pi); //
//拿到新进程的句柄
g_hInst = (HINSTANCE)pi.hProcess;
结束进程
- 主线程入口函数返回
- 在父进程的某个线程去调用TerminateProcess去结束一个子进程
- 当前进程的某个位置调用ExitProcess去结束这个子进程
//参数1,进程句柄,参数2,结束码
TerminateProcess(g_hInst,0);
//参数为结束码
EixtProcess(0);
线程Thread
线程特点:
- 生命周期会受到进程的影响
- 进程一旦被创建,系统必然会同时给出一个主线程
- 进程一旦死亡,这个进程里面的所有线程都会死亡
线程需要的开销比进程少,进程又是不活跃的,如果需要异步解决问题,便面创建进程,使用线程。
主要的函数列表
序号 | 函数名 | 功能 |
---|---|---|
1 | CreateThread() | 创建一个新线程 |
2 | ExitThread() | 正常结束一个线程的执行 |
3 | TerminateThead() | 强制终止一个线程的执行 |
4 | ResumeThread() | 重启一个线程 |
5 | SuspendThread() | 挂起一个线程 |
6 | GetExiCodeThread() | 得到一个线程的退出码 |
7 | GetThreadPriority() | 得到一个线程的优先级 |
8 | SetThreadPriority() | 设置一个线程的优先级 |
9 | CloseHandle() | 关闭一个线程的句柄 |
10 | CreateRemoteThread() | 再另一个进程中创建一个新线程 |
11 | PostThreadMessage() | 发送一条消息给指定的线程 |
12 | GetCurrentThread() | 得到当前的线程句柄 |
13 | GetCurrentThreadId() | 得到当前线程的ID |
14 | GetThreadId() | 得到指定线程的ID |
15 | WaitForSingleObject() | 等待单个对象 |
16 | WaitForMultipleObjects() | 等待多个对象 |
线程函数定义
线程函数的规范格式定义为
DWORD WINAPI ThreadProc (LPVOID lpParam);
但我常常看到有下列的线程函数定义:
void ThreadProc ();
但使用的时候要这样通过
LPTHREAD_START_ROUTINE转换,如:
(LPTHREAD_START_ROUTINE)ThreadProc
而且线程函数必须是全局函数,不能在类中声明和定义。
-
传递给线程执行函数的参数不能是局部变量,而且必须是参数的地址。如:
Int *pOffset = newint(10); CreateThread(NULL,0,ThreadProc,pOffset,0,&dwThreadID[i]);//正确
-
线程执行函数必须是全局函数。
创建线程
CreateThread将在主线程的基础上创建一个新线程,大致做如下步骤:
-
在内核对象中分配一个线程标识/句柄,可供管理,由CreateThread返回
-
把线程退出码置为STILL_ACTIVE,把线程挂起计数置1
-
分配context结构
-
分配两页的物理存储以准备栈,保护页设置为PAGE_READWRITE,第2页设为PAGE_GUARD
-
lpStartAddr和lpvThread值被放在栈顶,使它们成为传送给StartOfThread的参数
-
把context结构的栈指针指向栈顶(第5步)指令指针指向startOfThread函数
//创建线程函数
HANDLE CreateThread(
_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,//指向SECURITY_ATTRIBUTES型态的结构的指针
_In_ SIZE_T dwStackSize,//设置初始栈的大小,以字节为单位
_In_ LPTHREAD_START_ROUTINE lpStartAddress,//指向线程函数的指针
_In_opt_ __drv_aliasesMem LPVOID lpParameter,//向线程函数传递的参数,是一个指向结构的指针
_In_ DWORD dwCreationFlags,//线程标志
_Out_opt_ LPDWORD lpThreadId//保存新线程的id
);
//返回值:函数成功,返回线程句柄;函数失败返回false。若不想返回线程ID,设置值为NULL。
参数说明
lpThreadAttributes:指向SECURITY_ATTRIBUTES型态的结构的指针。在Windows 98中忽略该参数。在Windows NT中,NULL使用默认安全性,不可以被子线程继承,否则需要定义一个结构体将它的bInheritHandle成员初始化为TRUE
dwStackSize,设置初始栈的大小,以字节为单位,如果为0,那么默认将使用与调用该函数的线程相同的栈空间大小。任何情况下,Windows根据需要动态延长堆栈堆栈)的大小。
lpStartAddress,指向线程函数的指针,形式:@函数名,函数名称没有限制,
线程有两种声明方式
(1)DWORD WINAPI 函数名 (LPVOID lpParam); //标准格式
//线程执行函数
DWORD WINAPI funcName (LPVOID lpParam)
{
//代码执行
return 0;
}
//创建线程
CreateThread(NULL, 0, funcName, 0, 0, 0);
(2)void 函数名();
使用void 函数名()此种线程声明方式时,lpStartAddress需要加入LPTHREAD_START_ROUTINE转换,如
//线程执行函数
void funcName()
{
//代码执行
return;
}
//创建线程
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)funcName, 0, 0, 0);
lpParameter:向线程函数传递的参数,是一个指向结构的指针,不需传递参数时,为NULL。
dwCreationFlags :线程标志,可取值如下
值 | 描述 |
---|---|
CREATE_SUSPENDED | 创建一个挂起的线程(0x00000004) |
0 | 表示创建后立即激活 |
STACK_SIZE_PARAM_IS_A_RESERVATION | dwStackSize参数指定初始的保留堆栈 的大小,否则,dwStackSize指定提交的大小。该标记值在Windows 2000/NT and Windows Me/98/95上不支持。(0x00010000) |
lpThreadId:保存新线程的id。
代码示例
//全局变量
int g_piao = 100;
//线程执行函数
int getTicket()
{
while(true)
{
if(index>0)
{
index--;
}
else
{
break;
}
}
return 0;
}
//创建线程
int main()
{
int index=1;
//创建线程
HANDLE handle = CreateThread(nullptr, //指向一个结构体的指针
0, //线程堆栈打下,默认给0
getTicket, //开启线程的函数
(LPVOID)index,//传递给线程函数的参数
0, //线程创建的参数
nullptr); //新线程ID
return 0;
}
退出线程
- 主线程主动返回
- 某个线程里面调用 TerminateThread结束指定线程
- 某个线程调用ExitThread结束
//创建线程
int main()
{
int index=1;
//创建线程
HANDLE handle = CreateThread(nullptr, //指向一个结构体的指针
0, //线程堆栈打下,默认给0
getTicket, //开启线程的函数
(LPVOID)index,//传递给线程函数的参数
0, //线程创建的参数
nullptr); //新线程ID
//结束线程
TerminateThread(handle,0);
return 0;
}
同时也可以通过得到线程的退出码,来得到线程的状态来退出线程
得到退出码的函数
GetExitCodeThread()
//获取一个已中止线程的退出代码
BOOL GetExitCodeThread(HANDLE hThread, //线程句柄
LPDWORD lpExitCode); //用于保存线程退出代码的一个长整数变量
//参数二:如线程尚未中断,则设为常数STILL_ACTIVE
//返回值Long,非零表示成功,零表示失败。会设置GetLastError
判断线程的退出码如果是STILL_ACTIVE,表示线程任然处于活跃状态,非STILL_ACTIVE表示线程已经死亡。
代码演示如下:
//全局变量
int g_piao = 100;
//1.定义事件句柄
HANDLE g_hEvent;
//线程执行函数
void getTicket(LPVOID v)
{
while(true)
{
//3.进入临界区,使没有得到通知的线程在此资源等待
WaitForSingleObject(g_hEvent, //等待的事件
INFINITE);//等待时间,INFINITE表示一直等待
if(g_piao>0)
{
printf("第%d个代售点售出第%d张票。\n",int(v),g_piao);
g_piao--;
}
else
{
//4.变量为0跳出循环,设置事件通知
SetEvent(g_hEvent);
break;
}
//4.设置事件通知
SetEvent(g_hEvent);
}
return 0;
}
//创建线程
int main()
{
int index=1;
DWORD t1,t2,t3,t4; //用来存储线程退出码
//2.事件句柄的创建和初始化
g_hEvent = CreateEvent(nullptr, //事件的安全属性
false, //表示重置,是自动事件还是手动事件,false表示手动
true, //事件的初始状态,已通知还是未通知,true表示已通知
nullptr); //事件内核对象的名称
//创建四个线程
HANDLE handle1 = CreateThread(nullptr,0,(LPTHREAD_START_ROUTINE)getTicket,(LPVOID)index,0,nullptr);
index++;
HANDLE handle2 = CreateThread(nullptr,0,(LPTHREAD_START_ROUTINE)getTicket,(LPVOID)index,0,nullptr);
index++;
HANDLE handle3 = CreateThread(nullptr,0,(LPTHREAD_START_ROUTINE)getTicket,(LPVOID)index,0,nullptr);
index++;
HANDLE handle4 = CreateThread(nullptr,0,(LPTHREAD_START_ROUTINE)getTicket,(LPVOID)index,0,nullptr);
//主线程执行
while(true)
{
//3.进入临界区,使没有得到通知的线程在此资源等待
WaitForSingleObject(g_hEvent, //等待的事件
INFINITE);//等待时间,INFINITE表示一直等待
if(g_piao>0)
{
printf("火车站售出第%d张票。\n",g_piao);
g_piao--;
}
else
{
//4.变量为0跳出循环,设置事件通知
SetEvent(g_hEvent);
break;
}
//4.设置事件通知
SetEvent(g_hEvent);
}
//5. 关闭事件
CloseHandle(g_hEvent);
//获取线程的退出码
while(true)
{
//获取线程的退出码
GetExitCodeThread(handle1,&t1);
GetExitCodeThread(handle2,&t2);
GetExitCodeThread(handle3,&t3);
GetExitCodeThread(handle4,&t4);
//判断退出码状态
if(t1!=STILL_ACTIVE&&t2!=STILL_ACTIVE&&t3!=STILL_ACTIVE&&t4!=STILL_ACTIVE)
{
break;
}
}
//关闭线程
TerminateThread(handle1,0);
TerminateThread(handle2,0);
TerminateThread(handle3,0);
TerminateThread(handle4,0);
return 0;
}
线程通讯
两种情况下进行通讯
- 多个线程访问共享资源而不能让共享资源被破坏
- 当一个线程将某个任务完成之后,通知另一个线程接着去完成下一个任务
通过如下方式来实现通讯
线程互斥访问:用户区间和内核对象,其中内核对象不只用来做线程的互斥。
注:内核对象可以处于已通知和未通知的两种状态,状态的切换是系统的规则来决定。
在线程的运行过程中,线程内核对象处于未通知状态,当线程结束时,变为已通知(抢占CPU时间)。
临界区变量
属于用户区间
步骤如下
- 定义一个临界区变量:CRITICAL_SECTION g_cs;
- 变量初始化 :InitializeCriticalSection(&g_cs);
- 进入临界区锁定:EnterCriticalSection(&g_cs);
- 离开临界区解锁:LeaveCriticalSection(&g_cs);
- 临界区释放:DeleteCriticalSection(&g_cs);
代码示例如下
//全局变量
int g_piao = 100;
//1.定义临界区变量
CRITICAL_SECTION g_cs;
//线程执行函数
void getTicket(LPVOID v)
{
while(true)
{
//3.进入临界区
EnterCriticalSection(&g_cs);
if(g_piao>0)
{
printf("第%d个代售点售出第%d张票。\n",int(v),g_piao);
g_piao--;
}
else
{
//4.变量为0跳出循环,也离开临界区
LeaveCriticalSection(&g_cs);
break;
}
//4.离开临界区
LeaveCriticalSection(&g_cs);
}
return 0;
}
//创建线程
int main()
{
int index=1;
//2.临界区变量初始化
InitializeCriticalSection(&g_cs);
//创建四个线程
HANDLE handle1 = CreateThread(nullptr,0,(LPTHREAD_START_ROUTINE)getTicket,(LPVOID)index,0,nullptr);
index++;
HANDLE handle2 = CreateThread(nullptr,0,(LPTHREAD_START_ROUTINE)getTicket,(LPVOID)index,0,nullptr);
index++;
HANDLE handle3 = CreateThread(nullptr,0,(LPTHREAD_START_ROUTINE)getTicket,(LPVOID)index,0,nullptr);
index++;
HANDLE handle4 = CreateThread(nullptr,0,(LPTHREAD_START_ROUTINE)getTicket,(LPVOID)index,0,nullptr);
//主线程执行
while(true)
{
//3.进入临界区
EnterCriticalSection(&g_cs);
if(g_piao>0)
{
printf("火车站售出第%d张票。\n",g_piao);
g_piao--;
}
else
{
//4.变量为0跳出循环,也离开临界区
LeaveCriticalSection(&g_cs);
break;
}
//4.离开临界区
LeaveCriticalSection(&g_cs);
}
//5. 临界区释放
DeleteCriticalSection(&g_cs);
return 0;
}
事件
事件来处理线程的互斥,是基于内核对象的同步机制。
分为以下几步骤:
-
定义一个事件句柄:HANDLE g_hEvent;
-
函数内创建事件句柄,事件初始化
g_hEvent = CreateEvent(nullptr, //事件的安全属性 false, //表示重置,是自动事件还是手动事件,false表示手动 true, //事件的初始状态,已通知还是未通知,true表示已通知 nullptr); //事件内核对象的名称 //true 自动事件:表示事件得到通知,等待这个事件的线程只有一个线程变为可调度线程 //false 手动事件:表示事件得到通知,等待这个事件的所有线程变为可调度线程
-
进入临界区,事件等待通知
WaitForSingleObject(g_hEvent, //等待的事件 INFINITE);//等待时间,INFINITE表示一直等待
进入临界区(内核对象与用户区间有区别,内核对象会自动切换状态,不需要手动设置)
-
设置事件通知:SetEvent(g_hEvent)
-
关闭事件:CloseHandle(g_hEvent)
示例代码如下
//全局变量
int g_piao = 100;
//1.定义事件句柄
HANDLE g_hEvent;
//线程执行函数
void getTicket(LPVOID v)
{
while(true)
{
//3.进入临界区,使没有得到通知的线程在此资源等待
WaitForSingleObject(g_hEvent, //等待的事件
INFINITE);//等待时间,INFINITE表示一直等待
if(g_piao>0)
{
printf("第%d个代售点售出第%d张票。\n",int(v),g_piao);
g_piao--;
}
else
{
//4.变量为0跳出循环,设置事件通知
SetEvent(g_hEvent);
break;
}
//4.设置事件通知
SetEvent(g_hEvent);
}
return 0;
}
//创建线程
int main()
{
int index=1;
//2.事件句柄的创建和初始化
g_hEvent = CreateEvent(nullptr, //事件的安全属性
false, //表示重置,是自动事件还是手动事件,false表示手动
true, //事件的初始状态,已通知还是未通知,true表示已通知
nullptr); //事件内核对象的名称
//创建四个线程
HANDLE handle1 = CreateThread(nullptr,0,(LPTHREAD_START_ROUTINE)getTicket,(LPVOID)index,0,nullptr);
index++;
HANDLE handle2 = CreateThread(nullptr,0,(LPTHREAD_START_ROUTINE)getTicket,(LPVOID)index,0,nullptr);
index++;
HANDLE handle3 = CreateThread(nullptr,0,(LPTHREAD_START_ROUTINE)getTicket,(LPVOID)index,0,nullptr);
index++;
HANDLE handle4 = CreateThread(nullptr,0,(LPTHREAD_START_ROUTINE)getTicket,(LPVOID)index,0,nullptr);
//主线程执行
while(true)
{
//3.进入临界区,使没有得到通知的线程在此资源等待
WaitForSingleObject(g_hEvent, //等待的事件
INFINITE);//等待时间,INFINITE表示一直等待
if(g_piao>0)
{
printf("火车站售出第%d张票。\n",g_piao);
g_piao--;
}
else
{
//4.变量为0跳出循环,设置事件通知
SetEvent(g_hEvent);
break;
}
//4.设置事件通知
SetEvent(g_hEvent);
}
//5. 关闭事件
CloseHandle(g_hEvent);
return 0;
}
互斥对象
确保线程对单个资源的互斥访问权
包含一个使用数量,一个线程id和一个递归的计数器,行为和临界区变量有点类似,只不过互斥属于内核对象,临界区属于用户区间。
id用来标识系统中哪个线程当前拥有互斥对象
递归计数器用于标识这个线程拥有互斥对象的次数
规则如下:
- 如果线程id为0,表示无效id,没有线程拥有这个互斥对象,发出信号
- 如果id不为0,证明某个线程拥有互斥对象,不发信号
步骤如下
-
定义一个互斥句柄:HANDLE g_hMutex;
-
创建并初始化互斥句柄
g_hMutex = CreateMutex(nullptr, //互斥对象的安全属性 false, //某个线程是否拥有互斥对象,false表示没有线程拥有 nullptr) //递归计数器 //第二个参数如果为false,表示对象拥有的id和计数器都为0 //如果为true,表示对象拥有的id和调用线程的id,递归计数器++
-
互斥对象等待通知
WaitForSingleObject(g_hMutex); //互斥对象句柄
-
重置互斥对象内核:ReleaseMutex(g_hMutex);
-
关闭互斥对象:CloseHandle(g_hMutex)
代码演示如下:
//全局变量
int g_piao = 100;
//1.定义互斥对象句柄
HANDLE g_hMutex;
//线程执行函数
void getTicket(LPVOID v)
{
while(true)
{
//3.进入临界区,互斥对象等待通知
WaitForSingleObject(g_hMutex, //等待的互斥对象
INFINITE);//等待时间,INFINITE表示一直等待
if(g_piao>0)
{
printf("第%d个代售点售出第%d张票。\n",int(v),g_piao);
g_piao--;
}
else
{
//4.变量为0跳出循环,重置互斥内核
ReleaseMutex(g_hMutex);
break;
}
//4.重置互斥内核
ReleaseMutex(g_hMutex);
}
return 0;
}
//创建线程
int main()
{
int index=1;
//2.互斥对象句柄的创建和初始化
g_hMutex = CreateMutex(nullptr, //互斥对象的安全属性
false, //某个线程是否拥有互斥对象,false表示没有线程拥有
nullptr) //递归计数器
//创建四个线程
HANDLE handle1 = CreateThread(nullptr,0,(LPTHREAD_START_ROUTINE)getTicket,(LPVOID)index,0,nullptr);
index++;
HANDLE handle2 = CreateThread(nullptr,0,(LPTHREAD_START_ROUTINE)getTicket,(LPVOID)index,0,nullptr);
index++;
HANDLE handle3 = CreateThread(nullptr,0,(LPTHREAD_START_ROUTINE)getTicket,(LPVOID)index,0,nullptr);
index++;
HANDLE handle4 = CreateThread(nullptr,0,(LPTHREAD_START_ROUTINE)getTicket,(LPVOID)index,0,nullptr);
//主线程执行
while(true)
{
//3.进入临界区,互斥对象等待通知
WaitForSingleObject(g_hMutex, //等待的互斥对象
INFINITE);//等待时间,INFINITE表示一直等待
if(g_piao>0)
{
printf("火车站售出第%d张票。\n",g_piao);
g_piao--;
}
else
{
//4.变量为0跳出循环,重置互斥内核
ReleaseMutex(g_hMutex);
break;
}
//4.重置互斥内核
ReleaseMutex(g_hMutex);
}
//5. 关闭互斥对象
CloseHandle(g_hMutex);
return 0;
}
信号量
用于做资源计数的
规则:
- 当前资源数量大于0,可以去使用资源,发出信号
- 当前资源数量为0,没有可以使用的资源,不会发出信号
- 当前资源数量不允许为复数
- 当前资源数量不能大于最大资源数量
步骤如下:
-
定义信号量句柄:HANDLE g_hSemap;
-
信号量的创建和初始化
g_hSemap = CreateSemaphore(nullptr, //信号量的安全属性 1, //信号量的初始个数 1, //信号量的最大个数 nullptr); //信号量的名字,名字为空 //参数二:初始化信号量的个数 //参数三:信号量的最大个数 //参数四:信号量名字 //2,3参数是借用信号的特点,初始和最大都为1,如果有线程占用了,初始数量--,变为0,不发出信号。
-
等待信号量的信号通知
WaitForSingleObject(g_hSemap); //信号量对象句柄
-
重置信号量:ReleaseSemaphore(g_hSemap,1,0);
-
关闭信号量:CloseHandle(g_hSemap);
代码演示如下:
//全局变量
int g_piao = 100;
//1.定义信号量句柄
HANDLE g_hSemap;
//线程执行函数
void getTicket(LPVOID v)
{
while(true)
{
//3.进入临界区,信号量等待通知
WaitForSingleObject(g_hMutex, //等待的信号量
INFINITE);//等待时间,INFINITE表示一直等待
if(g_piao>0)
{
printf("第%d个代售点售出第%d张票。\n",int(v),g_piao);
g_piao--;
}
else
{
//4.变量为0跳出循环,重置信号量
ReleaseMutex(g_hSemap);
break;
}
//4.重置信号量
ReleaseMutex(g_hSemap);
}
return 0;
}
//创建线程
int main()
{
int index=1;
//2.信号量句柄的创建和初始化
g_hSemap = CreateSemaphore(nullptr, //信号量的安全属性
1, //信号量的初始个数
1, //信号量的最大个数
nullptr); //信号量的名字,名字为空
//创建四个线程
HANDLE handle1 = CreateThread(nullptr,0,(LPTHREAD_START_ROUTINE)getTicket,(LPVOID)index,0,nullptr);
index++;
HANDLE handle2 = CreateThread(nullptr,0,(LPTHREAD_START_ROUTINE)getTicket,(LPVOID)index,0,nullptr);
index++;
HANDLE handle3 = CreateThread(nullptr,0,(LPTHREAD_START_ROUTINE)getTicket,(LPVOID)index,0,nullptr);
index++;
HANDLE handle4 = CreateThread(nullptr,0,(LPTHREAD_START_ROUTINE)getTicket,(LPVOID)index,0,nullptr);
//主线程执行
while(true)
{
//3.进入临界区,信号量等待通知
WaitForSingleObject(g_hMutex, //等待的信号量
INFINITE);//等待时间,INFINITE表示一直等待
if(g_piao>0)
{
printf("火车站售出第%d张票。\n",g_piao);
g_piao--;
}
else
{
//4.变量为0跳出循环,重置信号量
ReleaseMutex(g_hSemap);
break;
}
//4.重置信号量
ReleaseMutex(g_hSemap);
}
//5. 关闭信号量
CloseHandle(g_hSemap);
return 0;
}