博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
VC++线程同步(二) Mutex互斥量的例子
阅读量:5958 次
发布时间:2019-06-19

本文共 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注释掉。

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


 本文转自超级极客51CTO博客,原文链接:http://blog.51cto.com/12158490/1950883,如需转载请自行联系原作者
你可能感兴趣的文章
初见Ajax——javascript访问DOM的三种访问方式
查看>>
SQL Server 字符串处理
查看>>
WPF Image控件 Source: Byte[] ,BitmapImage 相互转换
查看>>
Autofac 解释第一个例子 《第一篇》
查看>>
ROS学习(七)—— 理解ROS Topic
查看>>
BTARN 接收消息流以3A7为例
查看>>
C++指针理解
查看>>
大量数据并发的情况下 ,不用自增列 怎样设置主键
查看>>
windows中最重要的三个动态链接库及功能
查看>>
struts2操作json成字符串格式错误被转义及其前台访问json对象的方法
查看>>
HDU1004——Let the Balloon Rise
查看>>
vb.net 操作xml
查看>>
hbase 学习笔记二----shell
查看>>
C#:枚举
查看>>
P、NP、NPC、NP-Hard问题
查看>>
Linux 文件和目录管理之列出、删除、复制、移动及改名
查看>>
what is web farm
查看>>
C#集合--Dictionary
查看>>
[译]使用JMH进行微基准测试:不要猜,要测试!
查看>>
Tell Me About Yourself - Best Answers and Examples
查看>>