同步对象使用实例
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注释掉。
两个线程同时访问一个资源,进行读写,导致资源脱离控制。