语法:
BOOL CloseHandle( [in] HANDLE hObject );
函数说明:
hOnject:对象的有效句柄。
返回值:如果函数成功,则返回非零值。
我们来看一个新的线程安全问题:
#include <iostream> #include <windows.h> DWORD WINAPI ThreadProc1(LPVOID); DWORD WINAPI ThreadProc2(LPVOID); DWORD g_value = 0; HANDLE hMutex; int main() { hMutex = CreateMutex(NULL, FALSE, NULL); HANDLE Thread[2] = { 0 }; Thread[0]=CreateThread(NULL, 0, ThreadProc1, 0, NULL, NULL); Thread[1]=CreateThread(NULL, 0, ThreadProc2, 0, NULL, NULL); WaitForMultipleObjects(2, Thread, TRUE, INFINITE); printf("%d", g_value); return 0; } DWORD WINAPI ThreadProc1(LPVOID lpParameter) { while (1) { for (int i = 0; i < 10; i++) { std::cout << "++ "; Sleep(100); } std::cout << std::endl; } return 0; } DWORD WINAPI ThreadProc2(LPVOID lpParameter) { while (1) { for (int i = 0; i < 10; i++) { std::cout << "-- "; Sleep(100); } std::cout << std::endl; } return 0; }
我们的本意是输出十个“++”后,换行输出十个“-- ”,但是由于CPU时间片的问题,我们实际输出是这样的:
我们使用互斥体来解决:
#include <iostream> #include <windows.h> DWORD WINAPI ThreadProc1(LPVOID); DWORD WINAPI ThreadProc2(LPVOID); DWORD g_value = 0; HANDLE hMutex= CreateMutex(NULL, FALSE, NULL); int main() { HANDLE Thread[2] = { 0 }; Thread[0] = CreateThread(NULL, 0, ThreadProc1, 0, NULL, NULL); Thread[1] = CreateThread(NULL, 0, ThreadProc2, 0, NULL, NULL); WaitForMultipleObjects(2, Thread, TRUE, INFINITE); printf("%d", g_value); return 0; } DWORD WINAPI ThreadProc1(LPVOID lpParameter) { while (1) { WaitForSingleObject(hMutex,INFINITE); for (int i = 0; i < 10; i++) { std::cout << "++ "; Sleep(100); } std::cout << std::endl; ReleaseMutex(hMutex); } return 0; } DWORD WINAPI ThreadProc2(LPVOID lpParameter) { while (1) { WaitForSingleObject(hMutex,INFINITE); for (int i = 0; i < 10; i++) { std::cout << "-- "; Sleep(100); } std::cout << std::endl; ReleaseMutex(hMutex); } return 0; }
我们来看看运行效果:
可以发现我们使用互斥体解决了该线程安全问题。
四. 事件
前面介绍的都是线程互斥技术,在我们实际开发的过程中,有很多地方是有好几个线程相互依赖,这时候就需要线程同步技术了,这里首先我们来介绍事件:
这里给出官方文档地址:事件对象 (同步)
事件我个人理解为就是一个通知,当两个线程相互依赖的时候,其中一个线程完成了工作,就将事件设置为有信号(可以理解为发出了通知)另一个线程等待到事件消息后,开始工作。
事件的使用也较为简单,主要包括以下操作:
1. 创建事件
使用CreatEvent()
函数,这里给出官方文档地址:createEventA 函数 (synchapi.h)
函数功能:创建或打开命名或未命名的事件对象
语法:
HANDLE CreateEventA( [in, optional] LPSECURITY_ATTRIBUTES lpEventAttributes, [in] BOOL bManualReset, [in] BOOL bInitialState, [in, optional] LPCSTR lpName ); 6
参数说明:
lpEventAttributes:安全属性
bManualReset:如果设置为TRUE,则操作系统会自动重置事件,如果设置为FALSE,则需要程序员手动设置事件。
bInitialState:如果此参数为 TRUE,则会向事件对象发出初始状态信号;否则,它将不进行签名。
lpName:为事件命名
返回值:如果函数成功,则将返回事件对象句柄,如果函数失败,则返回NULL
2.多线程中使用事件
- 当在多线程中使用事件时,我们需要设置事件信号:
使用SetEvent()
函数,这里给出官方文档地址:SetEvent 函数 (synchapi.h)。
函数功能:将指定的事件对象设置为有信号状态。
语法:
BOOL SetEvent( [in] HANDLE hEvent );
参数说明:
hEnent:指定要设置的事件对象。
返回值:如果函数成功,则返回非零值,如果函数失败,则返回NULL。
当事件对象有信号后,在其他线程中可以打开事件对象:
使用OpenEvent()
函数,这里给出官方文档地址:OpenEventA 函数 (synchapi.h)。
函数功能:打开现有的命名事件对象
语法:
HANDLE OpenEventA( [in] DWORD dwDesiredAccess, [in] BOOL bInheritHandle, [in] LPCSTR lpName );
参数说明:
dwDesireAccess:访问事件对象。
bInheritHandle:如果此值为 TRUE,则此过程创建的进程将继承句柄。 否则,进程不会继承此句柄。
lpName:要打开的事件的名称。 名称比较区分大小写。
返回值:如果函数成功,则将返回事件对象的句柄,如果函数失败,则将返回NULL。
- 当一个线程使用事件对象结束后,可以将事件复位(无消息状态)
使用ResetEnent
函数,这里给出官方文档地址:ResetEvent 函数 (synchapi.h)。
函数功能:将事件对象设置为非对齐状态(无消息状态)
语法:
BOOL ResetEvent( [in] HANDLE hEvent );
参数说明:
hEnent:要设置的事件句柄。
返回值:如果函数成功,则返回非零值,如果函数失败,则返回NULL。
- 当事件使用结束后,可以使用
CloseHandel
函数来关闭事件。
我们来看看事件的使用:
这里创建了两个线程,当一个线程将全局变量g_value增加到100后,通知另一个线程工作。
#include <iostream> #include <windows.h> DWORD WINAPI ThreadProc1(LPVOID); DWORD WINAPI ThreadProc2(LPVOID); HANDLE hEvent = 0; DWORD g_value = 0; int main() { hEvent = CreateEvent(NULL, FALSE, 0, NULL); HANDLE Thread[2] = { 0 }; Thread[0] = CreateThread(NULL, 0, ThreadProc1, 0, NULL, NULL); Thread[1] = CreateThread(NULL, 0, ThreadProc2, 0, NULL, NULL); WaitForMultipleObjects(2, Thread, TRUE, INFINITE); printf("%d", g_value); CloseHandle(hEvent); return 0; } DWORD WINAPI ThreadProc1(LPVOID lpParameter) { for (int i = 0; i < 100; i++) { g_value++; } SetEvent(hEvent); printf("g_value已达到100,将进行输出\n"); return 0; } DWORD WINAPI ThreadProc2(LPVOID lpParameter) { WaitForSingleObject(hEvent, INFINITE); while (g_value<120) { for (int i = 0; i < 10; i++) { std::cout << "-- "; Sleep(1); } g_value++; std::cout << std::endl; } ResetEvent(hEvent); return 0; }
我们来看看这两个线程之间的协调工作:
五. 信号量
我们之前的线程同步和线程互斥技术,都是创建了对应的对象后,被相关线程获取后即可使用,那么有没有一种技术,能够指定被获取多少次呢?这样我们就可以控制相关的线程执行多少次了。
答案是有的,我们称之为信号量,这里给出官方文档地址:信号灯对象
信号灯对象是一个同步对象,用于维护零和指定最大值之间的计数。 每次线程完成信号灯对象的等待时,计数都会递减,每次线程释放信号灯时递增。 当计数达到零时,不会再成功等待信号灯对象状态发出信号。 当信号量计数大于零时,会将信号量的状态设置为已发出信号;当信号量计数为零时,会将信号量的状态设置为未发出信号。
那么当我们使用信号量对象时,操作主要包括为:创建信号量,增加信号量的计数,等待信号量,离开信号量,关闭信号量。
1. 创建信号量
使用CreateSemaphore
函数,这里给出官方文档地址:createSemaphoreA 函数 (winbase.h)
语法:
HANDLE CreateSemaphoreA( [in, optional] LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, [in] LONG lInitialCount, [in] LONG lMaximumCount, [in, optional] LPCSTR lpName );
参数说明:
lpSemaphoreAttributes:安全属性
lInitialCount:信号量的初始计数,此值必须小于lMaximumCount。
lMaximumCOunt:信号量的最大数量
lpName:为信号量命名
返回值:若函数成功,则返回信号量句柄,若函数失败,则返回NULL。
2. 等待信号量对象
使用WaitForSingluObject()
函数,前文已经介绍过,这里不再赘述。
3.增加信号量数量计数
使用ReleaseSemaphore
函数,这里给出官方文档地址:ReleaseSemaphore 函数 (synchapi.h)
函数功能:按指定量增加指定信号量的计数
语法:
BOOL ReleaseSemaphore( [in] HANDLE hSemaphore, [in] LONG lReleaseCount, [out, optional] LPLONG lpPreviousCount );
参数说明:
hSemaphore:指定信号量的句柄
lReleaseCount:指定要增加的数量
lpPreviousCount:这是一个OUT类型的参数,用于接收上一个计数,也就是增加之前的信号量计数
返回值:若函数成功,则返回非零值,若函数失败,则返回NULL。
4.关闭信号量
使用CloseHandle()
函数,前文已经介绍过,这里不再做赘述。
我们来看看信号量的使用:
#include <iostream> #include <windows.h> DWORD WINAPI ThreadProc1(LPVOID); DWORD WINAPI ThreadProc2(LPVOID); HANDLE hSemaphore = 0; DWORD g_value = 0; int main() { hSemaphore = CreateSemaphore(NULL, 0, 3, NULL); HANDLE Thread[2] = { 0 }; Thread[0] = CreateThread(NULL, 0, ThreadProc1, 0, NULL, NULL); Thread[1] = CreateThread(NULL, 0, ThreadProc2, 0, NULL, NULL); WaitForMultipleObjects(2, Thread, TRUE, INFINITE); printf("%d", g_value); CloseHandle(hSemaphore); return 0; } DWORD WINAPI ThreadProc1(LPVOID lpParameter) { for (int i = 0; i < 100; i++) { g_value++; } printf("g_value已达到100,将进行输出\n"); ReleaseSemaphore(hSemaphore, 2, NULL); return 0; } DWORD WINAPI ThreadProc2(LPVOID lpParameter) { for (int k = 0; k < 3; k++) { WaitForSingleObject(hSemaphore, INFINITE); for (int i = 0; i < 10; i++) { std::cout << "-- "; Sleep(1); } std::cout << std::endl; } return 0; }
我们来看看这段代码:我们本意是,当线程2执行的时候,输出三行,但是我们设置了信号量为2,所以只能输出两行。
我们看看执行效果:
本篇文章就分享到这里,如果大家发现其中有错误或者是个人理解不到位的地方,还请大家指出来,我会非常虚心地学习,希望我们共同进步!!!