内核对象
1、什么是内核对象

内核对象:
2、事件内核创建
1 2 3 4 5 6 7 8 9 10 11
| HANDLE CreateEventA( [in, optional] LPSECURITY_ATTRIBUTES lpEventAttributes, [in] BOOL bManualReset, [in] BOOL bInitialState, [in, optional] LPCSTR lpName );
HANDLE g_hEvent = CreateEvent(NULL,TRUE,FALSE,"XYZ");
HANDLE g_hMutex = CreateMutex(NULL,FALSE,"XYZ")
|
CreateEvent
函数参数解释:
lpEventAttributes
: 安全属性
bManualReset
: 如果值为TRUE,在获取事件对象后要手动设置未通知;如果为FALSE,则自动变成未通知
bInitialState
: 设置事件初始发送信号的状态,TRUE为初始已通知,FALSE为初始未通知
lpName
: 事件对象的名称。(只用在进程间才需要用到)
3、事件内核对象的获取
1 2 3 4 5 6 7 8 9 10
| HANDLE OpenEvent( DWORD dwDesiredAccess, BOOL bInheritHandle, LPCTSTR lpName );
HANDLE g_hEvent = OpenEvent(EVENT_ALL_ACCESS , FALSE,"XYZ");
HANDLE g_hMutex = OpenMutex(MUTEX_ALL_ACCESS,FALSE,"XYZ");
|
4、内核对象的销毁
BOOL CloseHandle(HANDLE hobj);
- 当没有其他程序引用时,系统会销毁内核对象(使用数量)
- 内核对象的生命周期,可能比创建它的对象要长
实验验证:
进程一创建内核对象,进程二获取内核对象,进程一销毁内核对象,进程三依旧可以获取内核对象,通俗个人理解:因为有两个人在使用,一个人销毁了,还有另外一个人可以找
- 首先要明白计数器的概念,在高2g内存(内核)有一个结构体存储着这些参数,进程一创建对象,计数器+1,进程二获取对象,计数器+1,进程一销毁对象,计数器-1,所以当进程三去获取内核对象时,计数器里面还剩1个可以获取
事件对象
1、事件对象的创建
1 2 3 4 5 6
| HANDLE CreateEvent( LPSECURITY_ATTRIBUTES lpEventAttributes, BOOL bManualReset, BOOL bInitialState, LPCTSTR lpName );
|
2、事件对象的控制
BOOL SetEvent(HANDLE hEvent);
// 将对象设置为已通知
对已通知、未通知的理解:
简单来说就是未通知时,不会发出信号,其他线程会在WaitForSingleObject的作用下阻塞,当对象变成已通知时,获取到对象的线程才能通过WaitForSingleObject进行执行下去
3、线程控制实验:只读形式的线程控制
实验一:
CreateEvent
的第二个参数设置成TRUE,即要手动设置成未通知
ThreadMain线程函数:

其他三个线程,开始时用WaitForSingleObject阻塞住:

当Thread1程序跑到SetEvent
后,三个文本框同时出现1000,主线程里修改对象状态为已通知时,第一个线程wait到了,执行完后状态依旧是已通知(因为我们第二个参数为TRUE,需要手动使用设置成未通知),所以三个编辑框都可以读取到

实验二:
CreateEvent
的第二个参数设置成FALSE,即Wait后自动设置成未通知
此时其他线程在函数执行完的位置要加上SetEvent(hEvent);来将事件对象设置成已通知

执行代码,下面三个文本框是一个一个变成1000的,因为设置成FALSE后当线程wait到事件对象时就会自动设置成未通知状态,其他线程就无法使用,得等到我这个线程用完并用SetEvent将其设置成已通知其他线程才能用,从而可以实现互斥
线程同步
1、什么是线程同步?
线程同步:
通俗来讲就是两个或以上的线程运行要严格按照规定的某种先后次序来运行,这种先后次序依赖于要完成的特定的任务。按预定的先后次序运行。比如 A 任务的运行依赖于 B 任务产生的数据。
线程互斥:
与线程同步不同,线程互斥只是当这个线程运行时,其他线程不能运行,得等到我这个线程运行完才能执行,没有顺序要求
2、代码实验
实现两个线程严格的交替输出。
首先先用互斥或者临界区来看看能不能实现:
代码1(临界区):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| CRITICAL_SECTION g_cs; int g_Max = 10; int g_Number = 0;
DWORD WINAPI ThreadProduct(LPVOID pM) { for (int i = 0; i < g_Max; i++) { EnterCriticalSection(&g_cs); g_Number = 1; DWORD id = GetCurrentThreadId(); printf("生产者%d将数据%d放入缓冲区\n", id, g_Number); LeaveCriticalSection(&g_cs);
} return 0; }
DWORD WINAPI ThreadConsumer(LPVOID pM) { for (int i = 0; i < g_Max; i++) { EnterCriticalSection(&g_cs); g_Number = 0; DWORD id = GetCurrentThreadId(); printf("----消费者%d将数据%d放入缓冲区\n", id, g_Number); LeaveCriticalSection(&g_cs); } return 0; }
int main(int argc, char* argv[]) { InitializeCriticalSection(&g_cs);
HANDLE hThread[2];
hThread[0] = ::CreateThread(NULL, 0, ThreadProduct, NULL, 0, NULL); hThread[1] = ::CreateThread(NULL, 0, ThreadConsumer, NULL, 0, NULL);
WaitForMultipleObjects(2, hThread, TRUE, INFINITE); CloseHandle(hThread[0]); CloseHandle(hThread[1]);
DeleteCriticalSection(&g_cs);
return 0; }
|
输出:

代码2(互斥体):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| HANDLE hMutex; int g_Max = 10; int g_Number = 0;
DWORD WINAPI ThreadProduct(LPVOID pM) { for (int i = 0; i < g_Max; i++) { WaitForSingleObject(hMutex, INFINITE); g_Number = 1; DWORD id = GetCurrentThreadId(); printf("生产者%d将数据%d放入缓冲区\n", id, g_Number); ReleaseMutex(hMutex); } return 0; }
DWORD WINAPI ThreadConsumer(LPVOID pM) { for (int i = 0; i < g_Max; i++) { WaitForSingleObject(hMutex, INFINITE); g_Number = 0; DWORD id = GetCurrentThreadId(); printf("----消费者%d将数据%d放入缓冲区\n", id, g_Number); ReleaseMutex(hMutex); } return 0; }
int main(int argc, char* argv[]) { hMutex = CreateMutex(NULL, FALSE, NULL);
HANDLE hThread[2];
hThread[0] = ::CreateThread(NULL, 0, ThreadProduct, NULL, 0, NULL); hThread[1] = ::CreateThread(NULL, 0, ThreadConsumer, NULL, 0, NULL);
WaitForMultipleObjects(2, hThread, TRUE, INFINITE); CloseHandle(hThread[0]); CloseHandle(hThread[1]);
CloseHandle(hMutex);
return 0; }
|
输出:

发现不管时临界区,还是互斥体都无法实现严格的交替输出,这时,我们就可以用事件来实现。
事件是可以自动设置成未通知,然后通过SetEvent来设置成已通知的,对于两个线程,我们就可以通过两个事件来实现线程同步,具体代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| HANDLE hEvent1,hEvent2;
DWORD WINAPI ThreadProc1(LPVOID lpParameter) { for(int i = 0;i < 5;i++){ WaitForSingleObject(hEvent1, INFINITE); DWORD id = GetCurrentThreadId(); printf("生产者 %d 将数据 1 放入缓冲区\n",id); SetEvent(hEvent2); }
return 0; }
DWORD WINAPI ThreadProc2(LPVOID lpParameter) { for (int i = 0; i < 5; i++) { WaitForSingleObject(hEvent2, INFINITE); DWORD id = GetCurrentThreadId(); printf("----消费者 %d 将数据 1 取出缓冲区\n",id); SetEvent(hEvent1); }
return 0; }
int main() { HANDLE hThread[2]; hThread[0] = ::CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL); hThread[1] = ::CreateThread(NULL, 0, ThreadProc2, NULL, 0, NULL);
hEvent1 = CreateEvent(NULL, FALSE, TRUE, NULL); hEvent2 = CreateEvent(NULL, FALSE, FALSE, NULL);
WaitForMultipleObjects(2, hThread, TRUE, INFINITE); CloseHandle(hThread[0]); CloseHandle(hThread[1]); CloseHandle(hEvent1); CloseHandle(hEvent2);
return 0; }
|
可以看到输出已经是严格交替了:

总结:
- 线程同步的实现:可以用事件来实现,有几个线程就创建多少个事件对象,全部都设置成自动变成未通知状态,第一个启动的线程要在将事件对象的初始状态设置成已通知,然后通过一个线程控制另一个线程的启动。