同步对象使用实例
Win32窗口的建立:
我们将要学习的使用,分别是:互斥量,临界区,事件,信号量.所以我们需要一个窗口,呈现四种四种同步对象状态.
需要学到的目的有4点:
1 掌握内核同步对象的触发规则(是内核同步对象)
2 弄懂同步等待成功引起的副作用
3 了解各个同步对象的运行流程
4 明白内核同步对象和用户同步对象的异同点
一般掌握上面4种核心知识,就能放心大胆的使用多线程了。
首先创建一个Win32项目,不要选空项目;
我们需要四个小窗口,先找到注册主窗口的代码。
ATOM MyRegisterClass(HINSTANCE hInstance) { WNDCLASSEX wcex; wcex.cbSize = sizeof(WNDCLASSEX); wcex.style= CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc= WndProc; wcex.cbClsExtra= 0; wcex.cbWndExtra= 0; wcex.hInstance= hInstance; wcex.hIcon= LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WIN32PROJECT1)); wcex.hCursor= LoadCursor(NULL, IDC_ARROW); wcex.hbrBackground= (HBRUSH)(COLOR_WINDOW+1); wcex.lpszMenuName= MAKEINTRESOURCE(IDC_WIN32PROJECT1); wcex.lpszClassName= szWindowClass; wcex.hIconSm= LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL)); return RegisterClassEx(&wcex); }
不重要的部分(就是Win32窗口流程):先创建的是注册一个主窗口的Windows的类结构,并且赋值给他一个窗口的Proc函数,然后调用InitInstance创建一个主窗口.
子窗口创建: WM_CREATE 就是创建的
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
直接贴出完整代码:
其中WndProc1是子窗口的消息处理函数
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { int wmId, wmEvent; PAINTSTRUCT ps; HDC hdc; static int clientCX = 0; static int clientCY = 0; static TCHAR *szChildClass[] = { _T("Child1"), _T("Child2"), _T("Child3"), _T("Child4") };//子窗口名字 static WNDPROC childWndProc[] = { WndProc1, WndProc2, WndProc3, WndProc4 };//子窗口的消息处理函数 static HWND hwndChild[4]; //子窗口句柄 switch (message) { case WM_CREATE: { //对四个UI窗口类进行统一初始化 WNDCLASSEX wcex; wcex.cbSize = sizeof(WNDCLASSEX); wcex.style= CS_HREDRAW | CS_VREDRAW; wcex.cbClsExtra= 0; wcex.cbWndExtra= 0; wcex.hInstance= hInst; wcex.hIcon= NULL; wcex.hCursor= LoadCursor(NULL, IDC_ARROW); wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wcex.lpszMenuName= NULL; wcex.hIconSm= NULL; for (int i = 0; i < CHILD_WND_COUNT;++i) { //对不同的部分进行分别初始化 wcex.lpfnWndProc = childWndProc[i]; wcex.lpszClassName = szChildClass[i]; //注册窗口类 RegisterClassEx(&wcex); //创建窗口 并且记录窗口句柄 hwndChild[i] = CreateWindow( szChildClass[i], _T(""), WS_CHILD | WS_BORDER | WS_VISIBLE, 0, 0, 0, 0, hWnd, (HMENU)i, hInst, NULL); } } break; case WM_SIZE: { clientCX = LOWORD(lParam);//客户区的宽度 clientCY = HIWORD(lParam);//客户区的高度 for (int i = 0; i < CHILD_WND_COUNT; ++i) { // 移动窗口的位置和其大小 MoveWindow( hwndChild[i], (i % 2)*clientCX / 2, (i > 1)*clientCY / 2, clientCX / 2, clientCY / 2, TRUE ); } } break; case WM_COMMAND: wmId = LOWORD(wParam); wmEvent = HIWORD(wParam); // 分析菜单选择: switch (wmId) { case IDM_ABOUT: DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About); break; case IDM_EXIT: DestroyWindow(hWnd); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } break; case WM_PAINT: hdc = BeginPaint(hWnd, &ps); // TODO: 在此添加任意绘图代码... EndPaint(hWnd, &ps); break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; } LRESULT CALLBACK WndProc1(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { static THRPARAMS thrParams; int wmId, wmEvent; PAINTSTRUCT ps; HDC hdc; switch (message) { case WM_CREATE: { //系统中基于对话框字体的高度 int cyChar = HIWORD(GetDialogBaseUnits()); //填充THRPARAMS结构体 thrParams.hwnd = hWnd; thrParams.cyChar = cyChar; //创建一个当前线程没有拥有所有权的 互斥对象 } break; case WM_COMMAND: wmId = LOWORD(wParam); wmEvent = HIWORD(wParam); // 分析菜单选择: switch (wmId) { case IDM_ABOUT: DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About); break; case IDM_EXIT: DestroyWindow(hWnd); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } break; case WM_PAINT: hdc = BeginPaint(hWnd, &ps); // TODO: 在此添加任意绘图代码... EndPaint(hWnd, &ps); break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; }
1 演示创建一个Mutex互斥量
火车售票系统。
创建两个线程,演示如何进行资源的保护。两个线程竞争某个资源。
case WM_CREATE: { //系统中基于对话框字体的高度 int cyChar = HIWORD(GetDialogBaseUnits()); //填充THRPARAMS结构体 thrParams.hwnd = hWnd; thrParams.cyChar = cyChar; //创建一个当前线程没有拥有所有权的 互斥对象 //FALSE技术递归计数器为0 线程id为0 所以是触发状态 g_hMutex = CreateMutex(NULL, FALSE, NULL); //创建两个线程来卖火车票 HANDLE handleTicket1 = CreateThread(NULL, 0, ThrTicketProc1, &thrParams, 0, NULL); HANDLE handleTicket2 = CreateThread(NULL, 0, ThrTicketProc2, &thrParams, 0, NULL); /* 原因为:创建线程后返回了线程句柄,新创建的线程内核对象的使用计数是2,一个是线程本身,一个是 创建线程的线程,创建新的线程CloseHandle后,新的线程内核对象使用计数为1,当这个新线程结束运行后 内核对象的使用技术还要减1,这时内核对象的使用计数是0,则系统会自动删除新线程的内核对象,这是 正常的处理流程. 如果不调用CloseHandle()则新线程运行结束后,由于使用计数为1,所以不会删除线程的内核对象,这样 就会造成内存泄漏,当然在整个程序运行结束后,操作系统会回首这些内存,因此可以知道如果不调用 CloseHandle的话,该程序在运行阶段,会造成内存泄漏。 */ //关闭线程句柄 CloseHandle(handleTicket1); CloseHandle(handleTicket2); } //释放互斥量对象的句柄 在窗口关闭前 case WM_DESTROY: // 关闭 互斥量句柄内存 删除 CloseHandle(g_hMutex); PostQuitMessage(0); break;
来看这个线程函数怎么用:
两个线程都对火车票的票数,也就是全局变量,来进行操作。
我们要避免就是同时两个线程拿到这个变量,同时进行读写操作。
导致资源,脱离控制,要做到一个线程拿到这个资源立即锁定,只有他
完成,其他线程才能进行访问。
只需要修改其中的线程名输出就可以观测了.
DWORD WINAPI ThrTicketProc1(LPVOID lp) { //将输入的参数 转换成结构体 PPARAMS param = static_cast<PPARAMS>(lp); TCHAR szBuf[20] = { 0 }; HDC hdc; //进入死循环 while (true) { //等待函数 无限等待,知道g_hMutex这个互斥量对象触发。 //不需要判断返回值因为参数用的INFINITE,肯定有一个线程拿到这个所有权 WaitForSingleObject(g_hMutex, INFINITE); //如果票数大于0 g_trainTickets是一个全局变量 if (g_trainTickets > 0) { //在这里休眠 一下, 暂时放弃剩余的时间片 Sleep(800); //销售火车票 // 打印表示哪个线程销售的火车票 wsprintf(szBuf, _T("线程1剩余火车票:%d"), g_trainTickets--); // 获得绘图句柄 hdc = GetDC(param->hwnd); //将字体绘制到子窗口中 TextOut(hdc, 0, g_iLine*param->cyChar, szBuf, lstrlen(szBuf)); ReleaseDC(param->hwnd,hdc); //清空字符串 memset(szBuf, 0, sizeof(TCHAR) * 20); //全局变量行数 g_iLine++; //整个子窗口 重新绘制 InvalidateRect(param->hwnd,NULL,FALSE); //解锁释放 这个互斥量对象 使他触发 ReleaseMutex(g_hMutex); } else { //解锁释放 这个互斥量对象 使他触发 ReleaseMutex(g_hMutex); break; } } return 0; }
我门发现井然有序,如果我们不释放Release会造成死锁,这样其他
等待的线程,或永远在等待,不会被触发。
如果我们使用Wait等待函数,那WaitForSingleObject注释掉。
两个线程同时访问一个资源,进行读写,导致资源脱离控制。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。