本文共 6604 字,大约阅读时间需要 22 分钟。
同步对象使用实例
Win32窗口的建立:
我们将要学习的使用,分别是:互斥量,临界区,事件,信号量.所以我们需要一个窗口,呈现四种四种同步对象状态.
需要学到的目的有4点:
1 掌握内核同步对象的触发规则(是内核同步对象)
2 弄懂同步等待成功引起的副作用
3 了解各个同步对象的运行流程
4 明白内核同步对象和用户同步对象的异同点
一般掌握上面4种核心知识,就能放心大胆的使用多线程了。
首先创建一个Win32项目,不要选空项目;
我们需要四个小窗口,先找到注册主窗口的代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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是子窗口的消息处理函数
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 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 | 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互斥量
火车售票系统。
创建两个线程,演示如何进行资源的保护。两个线程竞争某个资源。
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 | 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 ; |
来看这个线程函数怎么用:
两个线程都对火车票的票数,也就是全局变量,来进行操作。
我们要避免就是同时两个线程拿到这个变量,同时进行读写操作。
导致资源,脱离控制,要做到一个线程拿到这个资源立即锁定,只有他
完成,其他线程才能进行访问。
只需要修改其中的线程名输出就可以观测了.
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 | 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注释掉。
两个线程同时访问一个资源,进行读写,导致资源脱离控制。