一 Windows的进程
1 进程
进程是一个容器,包含程序执行需要的代码和数据还有
资源等信息,Windows是一个多任务的操作系统,可以
同时执行多个进程。
2 进程特点
1)每个进程都有自己的ID
2)每个进程都有自己的地址空间,进程无法访问对方的
地址空间
3)每个进程都有安全属性
4)每个进程至少包含一个线程(主线程)
3 进程环境信息
3.1 获取环境信息
LPVOID GetEnvironmentStrings(VOID);
返回环境信息。
3.2 释放环境信息
BOOL FreeEnvironmentStrings(
LPTSTR lpszEnvironmentBlock // 环境信息的首地址
);
4 获取和设置环境变量
BOOL SetEnvironmentVariable(
LPCTSTR lpName, // 环境变量的名称
LPCTSTR lpValue // 环境变量的值
);
DWORD GetEnvironmentVariable(
LPCTSTR lpName, // 环境名称
LPTSTR lpBuffer, // 接收环境变量的值的BUFF
DWORD nSize // BUFF的大小
);
5 进程的信息
3.1 获取当前进程的ID
DWORD GetCurrentProcessId(VOID)
3.2 获取当前进程的句柄
HANDLE GetCurrentProcess(VOID)
返回进程伪句柄(-1),可以使用该句柄访问进程的所有操作。
6 进程的使用
6.1 创建进程
WinExec - 早期16位
ShellExecute - Shell操作
CreateProcess - 目前使用最多
BOOL CreateProcess(
LPCTSTR lpApplicationName, // 应用程序的名称(带路径)
LPTSTR lpCommandLine, // 命令行参数
LPSECURITY_ATTRIBUTES lpProcessAttributes, // 进程的安全属性(NULL)
LPSECURITY_ATTRIBUTES lpThreadAttributes, // 线程的安全属性(NULL)
BOOL bInheritHandles, // 进程的句柄是否可继承
DWORD dwCreationFlags, // 创建方式 (0表示立即运行,CREATE_SUSPENDED挂起)
LPVOID lpEnvironment, // 指向进程环境信息(NULL使用当前进程的环境信息)
LPCTSTR lpCurrentDirectory, // 进程的工作目录(NULL使用当前进程的工作目录)
LPSTARTUPINFO lpStartupInfo, // 返回进程的起始信息
LPPROCESS_INFORMATION lpProcessInformation // 返回进程和线程的ID和句柄
);
6.2 结束进程
结束当前进程
VOID ExitProcess(
UINT uExitCode // 退出码
);
结束指定进程
BOOL TerminateProcess(
HANDLE hProcess, // 进程句柄
UINT uExitCode // 退出码
);
6.3 打开进程(得到进程句柄)- 通过进程ID拿到进程句柄
HANDLE OpenProcess(
DWORD dwDesiredAccess, // 访问方式,PROCESS_ALL_ACCESS
BOOL bInheritHandle, // 进程句柄是否可继承,TRUE可继承
DWORD dwProcessId // 进程的ID
);//执行成功,返回进程句柄
6.4 关闭进程句柄 - 将其索引值变为-1
CloseHandle
6.5 进程间的等候
等候 可等候的句柄 的信号。
DWORD WaitForSingleObject(
HANDLE hHandle, // 等候的句柄
DWORD dwMilliseconds // 等候时间(INFINITE等候时间无限大),以毫秒为单位,指定为1000表示1秒
);
阻塞函数,等候句柄的信号,只有在
句柄有信号时 或 超出等候时间时,
才会结束等候。
进程句柄,线程句柄,事件句柄有 有信号 和 无信号 之分
二 Windows的线程
1 线程
Windows的线程是可以执行代码的实例,系统是以线程为
单位调度程序的,一个程序中可以有多个线程,实现多
任务管理。
线程的特点:
1)线程都有自己的ID
2)线程都有自己的安全属性
3)每个线程都有自己的内存栈
4)每个线程都有自己的寄存器信息
... ...
进程多任务和线程多任务:
进程多任务是每个进程都有自己(私有)的地址空间,
线程多任务是进程内的多个线程使用同一个地址空间
线程调度
将CPU的执行时间分成时间片,根据时间片依次执行不同的线程
线程轮询:
线程A-->线程B-->......-->线程A... ...
2 线程的使用
2.1 定义线程处理函数
DWORD WINAPI ThreadProc(
LPVOID lpParameter // 创建线程时,传递给处理函数的。
);
2.2 创建线程
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes, // 线程的安全属性(默认为NULL )
DWORD dwStackSize, // 线程栈的大小(0 - 默认为1M)
LPTHREAD_START_ROUTINE lpStartAddress, // 线程的处理函数的地址
LPVOID lpParameter, // 传递给线程处理函数的参数
DWORD dwCreationFlags, // 线程的创建方式
LPDWORD lpThreadId // 接收线程的ID的BUFF
);//执行成功,返回线程的句柄
创建方式:
CREATE_SUSPENDED - 创建之后,线程处于挂起状态
0 - 创建之后,线程立即执行。
2.3 结束线程
结束当前线程
VOID ExitThread(
DWORD dwExitCode // 退出码
);
结束指定线程
BOOL TerminateThread(
HANDLE hThread, // 线程的句柄
DWORD dwExitCode // 退出码
);
2.4 关闭线程句柄
CloseHandle
2.5 线程的挂起和执行
挂起 - 将一个正在运行的线程挂起
DWORD SuspendThread(
HANDLE hThread // 线程句柄。
);
执行 - 将一个正处于挂起的线程执行
DWORD ResumeThread(
HANDLE hThread // 线程句柄
);
2.6 线程的信息
GetCurrentThreadId - 获取当前线程的ID
DWORD GetCurrentThreadId(VOID);
GetCurrentThread - 获取当前线程的句柄
HANDLE GetCurrentThread(VOID);
打开线程,并得到线程句柄 - 通过线程ID拿到线程句柄
HANDLE OpenThread(
DWORD dwDesiredAccess,//访问方式
BOOL bInheritHandle,//句柄是否可继承
DWORD dwThreadId//线程ID
);
三 线程的局部存储
1 相关问题
多个线程同时使用一个全局变量或静态变量,其中一个
线程修改了变量的值,另外的线程会使用修改后的值,
当每个线程需要处理属于自己的数据时,这种方式会产
生数据覆盖的问题。
2 线程的局部存储
2.1 作用
将全局变量或静态变量定义TLS变量,系统会为变量
在不同的线程中创建不同副本,即 只是变量名称相同
,但是在各自线程中的内存空间不同。
2.2 使用方式(2种方式)
2.2.1 定义TLS变量
_declspec(thread)
2.2.2 使用TLS API (了解即可)
1) 分配TLS索引
DWORD TlsAlloc(VOID);//返回分配好的TLS索引号
2)将数据保存到TLS索引中
BOOL TlsSetValue(
DWORD dwTlsIndex, // TLS 索引号
LPVOID lpTlsValue // 数据的BUFF,要设置的值
);
3)从TLS索引中取出值
LPVOID TlsGetValue(
DWORD dwTlsIndex // TLS 索引号
);//返回存入的数据
4)释放TLS索引
BOOL TlsFree(
DWORD dwTlsIndex // TLS 索引号
);
四 线程的同步机制
1 多线程问题
线程A-->线程B....-->线程Z-->....线程A... ...
当线程A执行printf输出时,如果线程A的执行时间结束,
系统将线程A的相关信息(栈,寄存器)进行压栈保护,
同时线程B的相关信息恢复,然后执行线程B,线程B继续
输出字符。
由于线程A正在输出字符,线程B继续输出造成界面字符
发生混乱。
2 线程的同步技术
2.1 原子锁
2.2 临界区(段)
2.3 事件
2.4 互斥
2.5 信号量
2.6 可等候定时器
WaitForSingleObject - 等候单个
WaitForMultipleObjects - 等候多个
DWORD WaitForMultipleObjects(
DWORD nCount, // 等候的句柄的数量
CONST HANDLE *lpHandles, // 句柄的BUFF
BOOL fWaitAll, // 等候方式
DWORD dwMilliseconds // 等候时间(INFINITE等候时间无限大),以毫秒为单位,如1000表示1秒
);
等候方式:
TRUE - 表示所有句柄都有信号,才结束等候,解除阻塞。
此时返回所有信号的句柄在数组中的下标.
FALSE - 表示句柄中有一个有信号,就结束等候,解除阻塞。
此时返回值返回有信号的句柄在数组中的下标.
五 原子锁
1 相关的问题
多个线程对同一数据进行操作,会产生结果丢失。
当线程A执行g_nValue++时,如果线程切换时间正好是在
线程A将值保存到g_nValue之前,线程B继续执行
g_nValue++,那么当线程A再次被切换回来时,会将原来
线程A保存的值保存到g_nValue上,那么线程B的加法运算
就被覆盖了。
g_nValue++执行机制
ecx = g_nValue;
ecx = exc + 1;
-------------------------------------
g_nValue = ecx;
move ecx,dword ptr [g_nValue (00427e34)]
add ecx,1
mov dword ptr [g_nValue (00427e34)],ecx
2 原子锁的使用
2.1 原子锁
对单条指令的操作。
2.2 API
InterlockedIncrement - 对自加操作进行原子锁
LONG InterlockedIncrement(
LPLONG lpAddend // variable to increment
);
InterlockedDecrement - 对自减操作进行原子锁
LONG InterlockedDecrement(
LPLONG lpAddend // variable address
);
InterlockedCompareExchange - 如果第一个参数值==第三个参数值,用第二个参数的值取代第一个参数指向的值。
LONG InterlockedCompareExchange(
LPLONG Destination, // destination address
LONG Exchange, // exchange value
LONG Comperand // value to compare
);
InterlockedExchange - 用第二个参数的值取代第一个参数指向的值。
LONG InterlockedExchange(
LPLONG Target, // value to exchange
LONG Value // new value
);
原子锁的实现:
直接对数据所在的内存进行操作,并且任一瞬间
只能有一个线程访问。
执行效率比其它同步技术高
六 临界区
1 相关的问题
printf输出混乱的问题,多线程下同时使用一段代码。
2 临界区
锁定一段代码,防止多个线程同时使用该段代码。
3 临界区的使用
3.1 初始化一个临界区
VOID InitializeCriticalSection(
LPCRITICAL_SECTION lpCriticalSection // 临界区变量
);
3.2 进入临界区 - 添加到被锁定的代码之前
VOID EnterCriticalSection(
LPCRITICAL_SECTION lpCriticalSection // 临界区变量
);
3.3 离开临界区 - 添加到被锁定的代码之后
VOID LeaveCriticalSection(
LPCRITICAL_SECTION lpCriticalSection // 临界区变量
);
3.4 删除临界区
VOID DeleteCriticalSection(
LPCRITICAL_SECTION lpCriticalSection // 临界区变量
);
4 原子锁和临界区
原子锁 - 单条指令
临界区 - 单条或多条指令,执行效率没有原子锁高
七 互斥
1 相关问题
多线程下,代码或资源的共享使用
2 互斥的使用
2.1 创建互斥
HANDLE CreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes,// 安全属性(默认为NULL)
BOOL bInitialOwner, // 互斥拥有者标识
LPCTSTR lpName // 互斥的名称,可以为NULL
);//创建成功,返回互斥句柄。
bInitialOwner互斥拥有者标识:
TRUE - 调用CreateMutex的线程拥有互斥
FALSE - 创建时没有任何线程拥有互斥
2.2 等候互斥
WaitForSingleObject/WaitForMultipleObjects
互斥的等候 遵循 谁先等候 谁先得到互斥。
2.3 释放互斥
BOOL ReleaseMutex(
HANDLE hMutex // 互斥句柄
);
2.4 关闭句柄 CloseHandle
2.5 临界区和互斥的区别
临界区 - 用户态,执行效率高,只能在同一进程中使用
互 斥 - 内核级,执行效率低,可以使用互斥的名称,跨进程使用。
八 事件
1 相关问题
线程间的通讯问题。
2 事件的使用
2.1 创建事件
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes,// 事件的安全属性
BOOL bManualReset, // 事件复位方式 TRUE - 手动,FALSE - 自动
BOOL bInitialState, // 事件初始状态 TRUE - 有信号状态,FALSE - 无信号状态
LPCTSTR lpName // 事件的名称
);//执行成功,返回事件句柄
2.2 等候事件
WaitForSingleObject/WaitForMultipleObjects
2.3 触发事件 - 将事件从无信号状态置为有信号状态
BOOL SetEvent(
HANDLE hEvent // 事件句柄
);
复位事件 - 将事件从有信号状态置为无信号状态,如果是自动复位则不需要调用该函数
BOOL ResetEvent(
HANDLE hEvent // 事件句柄
);
2.4 关闭事件
CloseHandle
3 小心事件的死锁.
WaitForSingleObject(hEvent1, INFINITE);
......
SetEvent(hEvent2);
WaitForSingleObject(hEvent2, INFINITE);
......
SetEvent(hEvent1);
九 信号量
1 相关问题
类似事件,解决线程间的通讯问题,可以提供一个计数器
,可以设置次数。
2 信号量的使用
2.1 创建信号量
HANDLE CreateSemaphore(
LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,// 安全属性
LONG lInitialCount, // 指定初始信号量的值
LONG lMaximumCount, // 指定信号量最大值
LPCTSTR lpName // 信号量的名称
);//执行成功,返回信号量的句柄
2.2 等候信号量 WaitForSingleObject/WaitForMultipleObjects
每等候一次,信号量就减1, 直到信号量为0,阻塞。
2.3 释放信号量
BOOL ReleaseSemaphore(
HANDLE hSemaphore, // 信号量的句柄
LONG lReleaseCount, // 这个信号量对象在当前基础上所要增加的值,这个值必须大于0,如果信号量加上这个值会导致信号量的当前值大于信号量创建时指定的最大值,那么这个信号量的当前值不变,同时这个函数返回FALSE;
LPLONG lpPreviousCount // 释放前原来信号量的数量,可以NULL.
);
2.4 关闭信号量
CloseHandle
练习:WinTest
两个线程
当点击 e或者E 按键,两个线程执行结束
当点击 1-9 时,输出相应的次数
利用信号量和事件
一个线程负责输出,一个线程负责输入
getch(); - 获取按键的字符,头文件conio.h
十 可等候定时器
1 相关问题
按照指定的时间或时间间隔通知程序,精度高,100纳秒,准确度高。
100纳秒 只能用于 第一次启动的时间,间隔时间精度仍然是毫秒。
2 定时器的使用
2.1 创建定时器
HANDLE CreateWaitableTimer(
LPSECURITY_ATTRIBUTES lpTimerAttributes,// 安全属性
BOOL bManualReset, // 重置方式,FALSE-自动, TRUE-手动
LPCTSTR lpTimerName // 命名,可以为NULL
);//执行成功,返回定时器句柄
2.2 设置定时器时间(启动时间和间隔时间)
BOOL SetWaitableTimer(
HANDLE hTimer, // 定时器句柄
const LARGE_INTEGER *pDueTime, // 第一次启动时间
LONG lPeriod, // 间隔时间
PTIMERAPCROUTINE pfnCompletionRoutine, // APC回调函数
LPVOID lpArgToCompletionRoutine, // APC回调函数的参数
BOOL fResume // 待机时是否执行标记。
);
pDueTime 第一次启动时间 - 64位整数,以100纳秒为单位
正数 - 绝对时间,就是系统的具体时间,比如:年月日时分秒
负数 - 相对时间,就是和当前时间的时间差,比如:-10000000 = 1秒以后启动
lPeriod 间隔时间 - 毫秒为单位,时间间隔如果为0,定时器只执行1次。
fResume - 待机处理标识。
TRUE - 待机时,激活机器继续工作。
FALSE - 放弃通知,不做处理。
2.3 等候
WaitFor...(时间未到句柄无信号,时间到了句柄有信号)
2.4 关闭句柄
CloseHandle
注意:
可等候定时器 NT4.0 以后的版本才支持.
#define _WIN32_WINNT 0x0400
总结:
进程/线程句柄 - 当运行时,句柄无信号,运行结束,句柄有信号。
互斥 - 谁先执行WaitFor...谁先得到互斥,其它线程阻塞在WaitFor...
事件 - SetEvent/ResetEvent WaitFor...等候事件有信号
信号量 - 按照信号量的计数,通过WaitFor...
可等候定时器 - 时间未到句柄无信号,时间到了句柄有信号
1 进程
进程是一个容器,包含程序执行需要的代码和数据还有
资源等信息,Windows是一个多任务的操作系统,可以
同时执行多个进程。
2 进程特点
1)每个进程都有自己的ID
2)每个进程都有自己的地址空间,进程无法访问对方的
地址空间
3)每个进程都有安全属性
4)每个进程至少包含一个线程(主线程)
3 进程环境信息
3.1 获取环境信息
LPVOID GetEnvironmentStrings(VOID);
返回环境信息。
3.2 释放环境信息
BOOL FreeEnvironmentStrings(
LPTSTR lpszEnvironmentBlock // 环境信息的首地址
);
4 获取和设置环境变量
BOOL SetEnvironmentVariable(
LPCTSTR lpName, // 环境变量的名称
LPCTSTR lpValue // 环境变量的值
);
DWORD GetEnvironmentVariable(
LPCTSTR lpName, // 环境名称
LPTSTR lpBuffer, // 接收环境变量的值的BUFF
DWORD nSize // BUFF的大小
);
5 进程的信息
3.1 获取当前进程的ID
DWORD GetCurrentProcessId(VOID)
3.2 获取当前进程的句柄
HANDLE GetCurrentProcess(VOID)
返回进程伪句柄(-1),可以使用该句柄访问进程的所有操作。
6 进程的使用
6.1 创建进程
WinExec - 早期16位
ShellExecute - Shell操作
CreateProcess - 目前使用最多
BOOL CreateProcess(
LPCTSTR lpApplicationName, // 应用程序的名称(带路径)
LPTSTR lpCommandLine, // 命令行参数
LPSECURITY_ATTRIBUTES lpProcessAttributes, // 进程的安全属性(NULL)
LPSECURITY_ATTRIBUTES lpThreadAttributes, // 线程的安全属性(NULL)
BOOL bInheritHandles, // 进程的句柄是否可继承
DWORD dwCreationFlags, // 创建方式 (0表示立即运行,CREATE_SUSPENDED挂起)
LPVOID lpEnvironment, // 指向进程环境信息(NULL使用当前进程的环境信息)
LPCTSTR lpCurrentDirectory, // 进程的工作目录(NULL使用当前进程的工作目录)
LPSTARTUPINFO lpStartupInfo, // 返回进程的起始信息
LPPROCESS_INFORMATION lpProcessInformation // 返回进程和线程的ID和句柄
);
6.2 结束进程
结束当前进程
VOID ExitProcess(
UINT uExitCode // 退出码
);
结束指定进程
BOOL TerminateProcess(
HANDLE hProcess, // 进程句柄
UINT uExitCode // 退出码
);
6.3 打开进程(得到进程句柄)- 通过进程ID拿到进程句柄
HANDLE OpenProcess(
DWORD dwDesiredAccess, // 访问方式,PROCESS_ALL_ACCESS
BOOL bInheritHandle, // 进程句柄是否可继承,TRUE可继承
DWORD dwProcessId // 进程的ID
);//执行成功,返回进程句柄
6.4 关闭进程句柄 - 将其索引值变为-1
CloseHandle
6.5 进程间的等候
等候 可等候的句柄 的信号。
DWORD WaitForSingleObject(
HANDLE hHandle, // 等候的句柄
DWORD dwMilliseconds // 等候时间(INFINITE等候时间无限大),以毫秒为单位,指定为1000表示1秒
);
阻塞函数,等候句柄的信号,只有在
句柄有信号时 或 超出等候时间时,
才会结束等候。
进程句柄,线程句柄,事件句柄有 有信号 和 无信号 之分
二 Windows的线程
1 线程
Windows的线程是可以执行代码的实例,系统是以线程为
单位调度程序的,一个程序中可以有多个线程,实现多
任务管理。
线程的特点:
1)线程都有自己的ID
2)线程都有自己的安全属性
3)每个线程都有自己的内存栈
4)每个线程都有自己的寄存器信息
... ...
进程多任务和线程多任务:
进程多任务是每个进程都有自己(私有)的地址空间,
线程多任务是进程内的多个线程使用同一个地址空间
线程调度
将CPU的执行时间分成时间片,根据时间片依次执行不同的线程
线程轮询:
线程A-->线程B-->......-->线程A... ...
2 线程的使用
2.1 定义线程处理函数
DWORD WINAPI ThreadProc(
LPVOID lpParameter // 创建线程时,传递给处理函数的。
);
2.2 创建线程
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes, // 线程的安全属性(默认为NULL )
DWORD dwStackSize, // 线程栈的大小(0 - 默认为1M)
LPTHREAD_START_ROUTINE lpStartAddress, // 线程的处理函数的地址
LPVOID lpParameter, // 传递给线程处理函数的参数
DWORD dwCreationFlags, // 线程的创建方式
LPDWORD lpThreadId // 接收线程的ID的BUFF
);//执行成功,返回线程的句柄
创建方式:
CREATE_SUSPENDED - 创建之后,线程处于挂起状态
0 - 创建之后,线程立即执行。
2.3 结束线程
结束当前线程
VOID ExitThread(
DWORD dwExitCode // 退出码
);
结束指定线程
BOOL TerminateThread(
HANDLE hThread, // 线程的句柄
DWORD dwExitCode // 退出码
);
2.4 关闭线程句柄
CloseHandle
2.5 线程的挂起和执行
挂起 - 将一个正在运行的线程挂起
DWORD SuspendThread(
HANDLE hThread // 线程句柄。
);
执行 - 将一个正处于挂起的线程执行
DWORD ResumeThread(
HANDLE hThread // 线程句柄
);
2.6 线程的信息
GetCurrentThreadId - 获取当前线程的ID
DWORD GetCurrentThreadId(VOID);
GetCurrentThread - 获取当前线程的句柄
HANDLE GetCurrentThread(VOID);
打开线程,并得到线程句柄 - 通过线程ID拿到线程句柄
HANDLE OpenThread(
DWORD dwDesiredAccess,//访问方式
BOOL bInheritHandle,//句柄是否可继承
DWORD dwThreadId//线程ID
);
三 线程的局部存储
1 相关问题
多个线程同时使用一个全局变量或静态变量,其中一个
线程修改了变量的值,另外的线程会使用修改后的值,
当每个线程需要处理属于自己的数据时,这种方式会产
生数据覆盖的问题。
2 线程的局部存储
2.1 作用
将全局变量或静态变量定义TLS变量,系统会为变量
在不同的线程中创建不同副本,即 只是变量名称相同
,但是在各自线程中的内存空间不同。
2.2 使用方式(2种方式)
2.2.1 定义TLS变量
_declspec(thread)
2.2.2 使用TLS API (了解即可)
1) 分配TLS索引
DWORD TlsAlloc(VOID);//返回分配好的TLS索引号
2)将数据保存到TLS索引中
BOOL TlsSetValue(
DWORD dwTlsIndex, // TLS 索引号
LPVOID lpTlsValue // 数据的BUFF,要设置的值
);
3)从TLS索引中取出值
LPVOID TlsGetValue(
DWORD dwTlsIndex // TLS 索引号
);//返回存入的数据
4)释放TLS索引
BOOL TlsFree(
DWORD dwTlsIndex // TLS 索引号
);
四 线程的同步机制
1 多线程问题
线程A-->线程B....-->线程Z-->....线程A... ...
当线程A执行printf输出时,如果线程A的执行时间结束,
系统将线程A的相关信息(栈,寄存器)进行压栈保护,
同时线程B的相关信息恢复,然后执行线程B,线程B继续
输出字符。
由于线程A正在输出字符,线程B继续输出造成界面字符
发生混乱。
2 线程的同步技术
2.1 原子锁
2.2 临界区(段)
2.3 事件
2.4 互斥
2.5 信号量
2.6 可等候定时器
WaitForSingleObject - 等候单个
WaitForMultipleObjects - 等候多个
DWORD WaitForMultipleObjects(
DWORD nCount, // 等候的句柄的数量
CONST HANDLE *lpHandles, // 句柄的BUFF
BOOL fWaitAll, // 等候方式
DWORD dwMilliseconds // 等候时间(INFINITE等候时间无限大),以毫秒为单位,如1000表示1秒
);
等候方式:
TRUE - 表示所有句柄都有信号,才结束等候,解除阻塞。
此时返回所有信号的句柄在数组中的下标.
FALSE - 表示句柄中有一个有信号,就结束等候,解除阻塞。
此时返回值返回有信号的句柄在数组中的下标.
五 原子锁
1 相关的问题
多个线程对同一数据进行操作,会产生结果丢失。
当线程A执行g_nValue++时,如果线程切换时间正好是在
线程A将值保存到g_nValue之前,线程B继续执行
g_nValue++,那么当线程A再次被切换回来时,会将原来
线程A保存的值保存到g_nValue上,那么线程B的加法运算
就被覆盖了。
g_nValue++执行机制
ecx = g_nValue;
ecx = exc + 1;
-------------------------------------
g_nValue = ecx;
move ecx,dword ptr [g_nValue (00427e34)]
add ecx,1
mov dword ptr [g_nValue (00427e34)],ecx
2 原子锁的使用
2.1 原子锁
对单条指令的操作。
2.2 API
InterlockedIncrement - 对自加操作进行原子锁
LONG InterlockedIncrement(
LPLONG lpAddend // variable to increment
);
InterlockedDecrement - 对自减操作进行原子锁
LONG InterlockedDecrement(
LPLONG lpAddend // variable address
);
InterlockedCompareExchange - 如果第一个参数值==第三个参数值,用第二个参数的值取代第一个参数指向的值。
LONG InterlockedCompareExchange(
LPLONG Destination, // destination address
LONG Exchange, // exchange value
LONG Comperand // value to compare
);
InterlockedExchange - 用第二个参数的值取代第一个参数指向的值。
LONG InterlockedExchange(
LPLONG Target, // value to exchange
LONG Value // new value
);
原子锁的实现:
直接对数据所在的内存进行操作,并且任一瞬间
只能有一个线程访问。
执行效率比其它同步技术高
六 临界区
1 相关的问题
printf输出混乱的问题,多线程下同时使用一段代码。
2 临界区
锁定一段代码,防止多个线程同时使用该段代码。
3 临界区的使用
3.1 初始化一个临界区
VOID InitializeCriticalSection(
LPCRITICAL_SECTION lpCriticalSection // 临界区变量
);
3.2 进入临界区 - 添加到被锁定的代码之前
VOID EnterCriticalSection(
LPCRITICAL_SECTION lpCriticalSection // 临界区变量
);
3.3 离开临界区 - 添加到被锁定的代码之后
VOID LeaveCriticalSection(
LPCRITICAL_SECTION lpCriticalSection // 临界区变量
);
3.4 删除临界区
VOID DeleteCriticalSection(
LPCRITICAL_SECTION lpCriticalSection // 临界区变量
);
4 原子锁和临界区
原子锁 - 单条指令
临界区 - 单条或多条指令,执行效率没有原子锁高
七 互斥
1 相关问题
多线程下,代码或资源的共享使用
2 互斥的使用
2.1 创建互斥
HANDLE CreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes,// 安全属性(默认为NULL)
BOOL bInitialOwner, // 互斥拥有者标识
LPCTSTR lpName // 互斥的名称,可以为NULL
);//创建成功,返回互斥句柄。
bInitialOwner互斥拥有者标识:
TRUE - 调用CreateMutex的线程拥有互斥
FALSE - 创建时没有任何线程拥有互斥
2.2 等候互斥
WaitForSingleObject/WaitForMultipleObjects
互斥的等候 遵循 谁先等候 谁先得到互斥。
2.3 释放互斥
BOOL ReleaseMutex(
HANDLE hMutex // 互斥句柄
);
2.4 关闭句柄 CloseHandle
2.5 临界区和互斥的区别
临界区 - 用户态,执行效率高,只能在同一进程中使用
互 斥 - 内核级,执行效率低,可以使用互斥的名称,跨进程使用。
八 事件
1 相关问题
线程间的通讯问题。
2 事件的使用
2.1 创建事件
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes,// 事件的安全属性
BOOL bManualReset, // 事件复位方式 TRUE - 手动,FALSE - 自动
BOOL bInitialState, // 事件初始状态 TRUE - 有信号状态,FALSE - 无信号状态
LPCTSTR lpName // 事件的名称
);//执行成功,返回事件句柄
2.2 等候事件
WaitForSingleObject/WaitForMultipleObjects
2.3 触发事件 - 将事件从无信号状态置为有信号状态
BOOL SetEvent(
HANDLE hEvent // 事件句柄
);
复位事件 - 将事件从有信号状态置为无信号状态,如果是自动复位则不需要调用该函数
BOOL ResetEvent(
HANDLE hEvent // 事件句柄
);
2.4 关闭事件
CloseHandle
3 小心事件的死锁.
WaitForSingleObject(hEvent1, INFINITE);
......
SetEvent(hEvent2);
WaitForSingleObject(hEvent2, INFINITE);
......
SetEvent(hEvent1);
九 信号量
1 相关问题
类似事件,解决线程间的通讯问题,可以提供一个计数器
,可以设置次数。
2 信号量的使用
2.1 创建信号量
HANDLE CreateSemaphore(
LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,// 安全属性
LONG lInitialCount, // 指定初始信号量的值
LONG lMaximumCount, // 指定信号量最大值
LPCTSTR lpName // 信号量的名称
);//执行成功,返回信号量的句柄
2.2 等候信号量 WaitForSingleObject/WaitForMultipleObjects
每等候一次,信号量就减1, 直到信号量为0,阻塞。
2.3 释放信号量
BOOL ReleaseSemaphore(
HANDLE hSemaphore, // 信号量的句柄
LONG lReleaseCount, // 这个信号量对象在当前基础上所要增加的值,这个值必须大于0,如果信号量加上这个值会导致信号量的当前值大于信号量创建时指定的最大值,那么这个信号量的当前值不变,同时这个函数返回FALSE;
LPLONG lpPreviousCount // 释放前原来信号量的数量,可以NULL.
);
2.4 关闭信号量
CloseHandle
练习:WinTest
两个线程
当点击 e或者E 按键,两个线程执行结束
当点击 1-9 时,输出相应的次数
利用信号量和事件
一个线程负责输出,一个线程负责输入
getch(); - 获取按键的字符,头文件conio.h
十 可等候定时器
1 相关问题
按照指定的时间或时间间隔通知程序,精度高,100纳秒,准确度高。
100纳秒 只能用于 第一次启动的时间,间隔时间精度仍然是毫秒。
2 定时器的使用
2.1 创建定时器
HANDLE CreateWaitableTimer(
LPSECURITY_ATTRIBUTES lpTimerAttributes,// 安全属性
BOOL bManualReset, // 重置方式,FALSE-自动, TRUE-手动
LPCTSTR lpTimerName // 命名,可以为NULL
);//执行成功,返回定时器句柄
2.2 设置定时器时间(启动时间和间隔时间)
BOOL SetWaitableTimer(
HANDLE hTimer, // 定时器句柄
const LARGE_INTEGER *pDueTime, // 第一次启动时间
LONG lPeriod, // 间隔时间
PTIMERAPCROUTINE pfnCompletionRoutine, // APC回调函数
LPVOID lpArgToCompletionRoutine, // APC回调函数的参数
BOOL fResume // 待机时是否执行标记。
);
pDueTime 第一次启动时间 - 64位整数,以100纳秒为单位
正数 - 绝对时间,就是系统的具体时间,比如:年月日时分秒
负数 - 相对时间,就是和当前时间的时间差,比如:-10000000 = 1秒以后启动
lPeriod 间隔时间 - 毫秒为单位,时间间隔如果为0,定时器只执行1次。
fResume - 待机处理标识。
TRUE - 待机时,激活机器继续工作。
FALSE - 放弃通知,不做处理。
2.3 等候
WaitFor...(时间未到句柄无信号,时间到了句柄有信号)
2.4 关闭句柄
CloseHandle
注意:
可等候定时器 NT4.0 以后的版本才支持.
#define _WIN32_WINNT 0x0400
总结:
进程/线程句柄 - 当运行时,句柄无信号,运行结束,句柄有信号。
互斥 - 谁先执行WaitFor...谁先得到互斥,其它线程阻塞在WaitFor...
事件 - SetEvent/ResetEvent WaitFor...等候事件有信号
信号量 - 按照信号量的计数,通过WaitFor...
可等候定时器 - 时间未到句柄无信号,时间到了句柄有信号