1.内核对象
内核对象是由操作系创建和维护的,在程序的高2g内存中创建;
进程可以共享内核对象;
常见的内核对象:进程、线程、文件、文件映射、事件、互斥体等等
1)内核对象的创建
各种内核对象有各自的创建API函数;
内核对象是由操作系统创建的,三环程序只能用API函数告诉系统需要创建一个内核对象;
例如:创建事件和互斥体
HANDLE g_hEvent = CreateEvent(NULL, TRUE, FALSE, "XYZ"); HANDLE g_hMutex = CreateMutex(NULL,FALSE, "XYZ");
windows在创建完内核对象后会返回该内核对象的句柄;
句柄实际上是指针的指针;
不直接返回内核对象地址的原因是为了安全;如果高2g的程序通过地址直接修改了内核对象,可能会导致系统崩溃;
2)获取内核对象
获取事件的API
HANDLE OpenEvent( DWORD dwDesiredAccess, // access BOOL bInheritHandle, // inheritance option LPCTSTR lpName // object name );
实例:获取事件和互斥体
HANDLE g_hEvent = OpenEvent(EVENT_ALL_ACCESS, FALSE, "XYZ"); HANDLE g_hMutex = OpenMutex(MUTEX_ALL_ACCESS,FALSE, "XYZ");
3)内核对象销毁
API函数:
BOOL CloseHandle(HANDLE hobj);
只是告诉操作系统,这个内核对象不用了,实际上该内核对象是否销毁还要看操作系统;
在创建完内核对象后,低2g会有一个结构体保存内核对象的信息;
结构体中有一个成员为计数器,每当有一个进程用openXXX调用内核对象时,计数器加1,调用CloseHandle时计数器减1;
当计数器为0时表示没有进程使用内核对象了,操作系统将销毁该对象;
1】当没有其他程序引用时,系统会销毁内核对象(使用数量).
2】内核对象的生命周期,可能比创建它的对象要长. (其它进程调用open方法得到了内核进程的句柄时)
2.事件
事件是一种内核对象;
1)创建事件
HANDLE CreateEvent( LPSECURITY_ATTRIBUTES lpEventAttributes, // 安全属性 NULL时为系统默认 BOOL bManualReset, // TRUE 通过调用ResetEvent将事件对象标记为未通知 BOOL bInitialState, // TRUE 已通知状态 FALSE未通知状态 LPCTSTR lpName // 对象名称 以NULL结尾的字符串 );
bManualReset ->如果为FALSE,只要有线程wait到了该事件,立马会将该事件的状态改回去变成未通知状态;为TRUE只能wait完后自己写代码来修改为未通知状态;
bInitialState ->表示事件创建时是否为已通知状态;如果为TRUE有线程wait该事件时在创建时立马就wait到了;
2)事件控制
BOOL SetEvent(HANDLE hEvent); //将对象设置为已通知
3)关闭事件句柄
BOOL CloseHandle(HANDLE hobj);
4)实例
创建一个窗口程序;
点击按钮go时会给文本框1赋值,然后开启3个线程分别将文本框1的值复制到文本框2、3、4中;
大体思路:
在go的点击事件中创建一个线程1,用来给文本框1赋值;
在线程1中创建一个事件a;然后在线程1中创建3个线程2、3、4;分别用来将文本框1中的值复制到文本框2、3、4中;
线程2、3、4都调用wait函数等待事件a;当事件a变成已通知状态后,3个线程的wait都将被触发,完成复制操作;
代码:
#include<windows.h> #include<stdio.h> #include "resource.h" HANDLE g_hEvent; HWND hEdit1; HWND hEdit2; HWND hEdit3; HWND hEdit4; DWORD WINAPI ThreadProc2(LPVOID lpParameter) { TCHAR szBuffer[10] = {0}; //当事件变成已通知时 WaitForSingleObject(g_hEvent, INFINITE); //读取内容 GetWindowText(hEdit1,szBuffer,10); SetWindowText(hEdit2,szBuffer); return 0; } DWORD WINAPI ThreadProc3(LPVOID lpParameter) { TCHAR szBuffer[10] = {0}; //当事件变成已通知时 WaitForSingleObject(g_hEvent, INFINITE); //读取内容 GetWindowText(hEdit1,szBuffer,10); SetWindowText(hEdit3,szBuffer); return 0; } DWORD WINAPI ThreadProc4(LPVOID lpParameter) { TCHAR szBuffer[10] = {0}; //当事件变成已通知时 WaitForSingleObject(g_hEvent, INFINITE); //读取内容 GetWindowText(hEdit1,szBuffer,10); SetWindowText(hEdit4,szBuffer); return 0; } DWORD WINAPI ThreadProc1(LPVOID lpParameter) { //创建事件 //默认安全属性 手动设置未通知状态(TRUE) 初始状态未通知 没有名字 g_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); HANDLE hThread[3]; //创建3个线程 hThread[0] = ::CreateThread(NULL, 0, ThreadProc2, NULL, 0, NULL); hThread[1] = ::CreateThread(NULL, 0, ThreadProc3, NULL, 0, NULL); hThread[2] = ::CreateThread(NULL, 0, ThreadProc4, NULL, 0, NULL); //设置文本框的值 SetWindowText(hEdit1,"1000"); //设置事件为已通知 SetEvent(g_hEvent); //等待线程结束 销毁内核对象 WaitForMultipleObjects(3, hThread, TRUE, INFINITE); CloseHandle(hThread[0]); CloseHandle(hThread[1]); CloseHandle(hThread[2]); CloseHandle(g_hEvent); return 0; } //回调函数 BOOL CALLBACK DialogProc( HWND hwndDlg, // handle to dialog box UINT uMsg, // message WPARAM wParam, // first message parameter LPARAM lParam // second message parameter ) { switch(uMsg) { case WM_INITDIALOG : hEdit1 = GetDlgItem(hwndDlg, TXT_1); hEdit2 = GetDlgItem(hwndDlg, TXT_2); hEdit3 = GetDlgItem(hwndDlg, TXT_3); hEdit4 = GetDlgItem(hwndDlg, TXT_4); return TRUE; case WM_COMMAND : switch (LOWORD (wParam)) { case BTN_GO: //额外创建一个线程来控制其它线程,防止主线程阻塞 HANDLE hThread = ::CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL); //如果不在其他的地方引用它 关闭句柄 ::CloseHandle(hThread); return TRUE; return FALSE; } break ; case WM_CLOSE: EndDialog(hwndDlg, 0); return TRUE; } return FALSE ; } //程序入口 int CALLBACK WinMain( HINSTANCE hInstance, HINSTANCE hPrevHinstance, LPSTR lpCmdLine, int nCmdShow ){ //创建对话框窗口 DialogBox(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc); return 0; }
结果:
因为事件的bManualReset为TRUE,其中一个线程等到事件后不会吧事件改为未通知状态,因此其它两个线程也会等到已通知状态的事件,所以3个文本框都复制成功;
如果将bManualReset设为FALSE,将只有一个文本框复制成功;
因为bManualReset位FALSE时,一个线程等到事件后,将会把事件改为未通知,其它线程将永远等不到已通知的事件而阻塞;
bManualReset为FALSE时的事件和互斥体的作用相似;只需要在线的功能执行完后手动调用SetEvent函数将事件重新设为已通知状态即可;
3.线程的同步
如果想多线程程序,需要解决两个问题:互斥和同步;
互斥是为了解决多个线程访问同一资源时的线程安全问题;
同步是为了让多个线程按自己需要的顺序来执行;
例如让线程1和线程2交替执行,而不是让线程1执行多次再执行线程2;
线程同步的应用场景:
例如线程a将文本读入内存;线程1分析文本中字数,线程2分析文本中的标点符号数;
此时需要线程a执行完后线程1、2才能执行;
1)用临界区和互斥体实现同步
例如:
有一个生产者线程和一个消费者线程;
需求是生产者线程将全局变量的值设为1,然后消费者线程将读到全局变量的值将其设为0;
用临界区和互斥体的作用差不多,都用来实现对全局变量的互斥访问;
但临界区和互斥体并不能保证生产者和消费者线程的访问顺序;
只能定义一个全局变量a用做标记,每次线程执行时先判断该标记的值来决定是否执行线程的功能;
这种办法效率低下,例如生产者线程被分配了cpu资源,此时判断标记得知全局变量的值不为0,表示没必要修改;但分配 到的cpu资源不会释放;
代码:
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)用事件实现线程同步
事件可以做到线程互斥,也能做到简单的线程同步;
思路:
创建两个事件:set和clear;
两个事件的bManualReset都设为FALSE,表示不需要手动修改事件为未通知状态;
set的bInitialState设为,TRUE表示当事件创建时就是已通知状态;
生产者线程等待set事件;因为set线程创建时就已通知状态,开始就能将全局变量设为1;
当生产者线程执行完后,将clear线程设为已通知状态;因为bManualReset位FALSE,set事件自动变为未通知状态,生产者线程阻塞;
消费者线程等待clear事件;
clear事件的bInitialState为FALSE,创建时为未通知状态,因此消费者线程将等待;
直到生产者线程将clear事件变成了已通知后,消费者线程继续执行;
消费者线程执行完后set事件自动变成未通知状态,消费者线程阻塞;
在消费者线程执行完前,调用函数将set事件变成已通知状态,因此生产者线程又能继续执行;
如此实现了两个线程的交替执行;
这样实现的同步效率较高,当线程无法执行时会进入等待状态cpu不会给它分配资源;
代码:
HANDLE g_hSet, g_hClear; int g_Max = 10; int g_Number = 0; //生产者线程函数 DWORD WINAPI ThreadProduct(LPVOID pM) { for (int i = 0; i < g_Max; i++) { WaitForSingleObject(g_hSet, INFINITE); g_Number = 1; DWORD id = GetCurrentThreadId(); printf("生产者%d将数据%d放入缓冲区\n",id, g_Number); SetEvent(g_hClear); } return 0; } //消费者线程函数 DWORD WINAPI ThreadConsumer(LPVOID pM) { for (int i = 0; i < g_Max; i++) { WaitForSingleObject(g_hClear, INFINITE); g_Number = 0; DWORD id = GetCurrentThreadId(); printf("----消费者%d将数据%d放入缓冲区\n",id, g_Number); SetEvent(g_hSet); } return 0; } int main(int argc, char* argv[]) { HANDLE hThread[2]; g_hSet = CreateEvent(NULL, FALSE, TRUE, NULL); g_hClear = CreateEvent(NULL, FALSE, FALSE, NULL); 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(g_hSet); CloseHandle(g_hClear); return 0; }