VC++线程同步(二) Mutex互斥量的例子

简介:

   同步对象使用实例 


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);
}

wKiom1l27SywF0z2AAGN7pRCKeQ723.png-wh_50


不重要的部分(就是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会造成死锁,这样其他

等待的线程,或永远在等待,不会被触发。
wKiom1l3AeCiiZptAACss5pn6T8690.png-wh_50


如果我们使用Wait等待函数,那WaitForSingleObject注释掉。

两个线程同时访问一个资源,进行读写,导致资源脱离控制。


wKiom1l3ArCgZeUJAAA22NLJKB0623.png-wh_50





 本文转自超级极客51CTO博客,原文链接:http://blog.51cto.com/12158490/1950883,如需转载请自行联系原作者





相关文章
|
2月前
|
安全 C++ 开发者
【C++多线程同步】C++多线程同步和互斥的关键:std::mutex和相关类的全面使用教程与深度解析
【C++多线程同步】C++多线程同步和互斥的关键:std::mutex和相关类的全面使用教程与深度解析
18 0
|
9月前
|
Java 调度 C++
C++并发与多线程(五)互斥量,atomic、与线程池(下)
C++并发与多线程(五)互斥量,atomic、与线程池(下)
|
8月前
|
C++
C++11/14/17中提供的mutex系列区别
C++11/14/17中提供的mutex系列类型如下:
|
9月前
|
Java C++
C++并发与多线程(五)互斥量,atomic、与线程池(上)
C++并发与多线程(五)互斥量,atomic、与线程池(上)
|
11月前
|
调度 C++
C++11之线程库(Thread、Mutex、atomic、lock_guard、同步)
C++11之线程库(Thread、Mutex、atomic、lock_guard、同步)
138 0
|
安全 Go 数据安全/隐私保护
Go context 原理(channel广播机制 + mutex线程安全)
Go context 原理(channel广播机制 + mutex线程安全)
331 0
|
API C# Windows
C#多线程(4):进程同步Mutex类
C#多线程(4):进程同步Mutex类
215 0
C#多线程(4):进程同步Mutex类
|
安全 算法 前端开发
【Example】C++ 标准库 std::thread 与 std::mutex
与 Unix 下的 thread 不同的是,C++ 标准库当中的 std::thread 功能更加简单,可以支持跨平台特性。 因此在应用需要跨平台的情况下,应优先考虑使用 std::thread。 同时为了使多线程操作更加安全,std::thread 经常与标准库互斥量 std::mutex 相配合使用。
333 0
|
安全 C# 数据安全/隐私保护
C#(四十三)之线程Mutex互斥
Mutex(互斥体): 排他性的使用共享资源称为线程间的互斥。 使用Mutex类要比使用monitor类消耗更多的系统资源,但他可以跨越多个应用程序,在多个应用程序间同步。
177 0
C#(四十三)之线程Mutex互斥
|
安全
VC++关于使用WaitForSingleObject等待线程安全退出,出现死机问题的解决
VC++关于使用WaitForSingleObject等待线程安全退出,出现死机问题的解决
487 0