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,如需转载请自行联系原作者





相关文章
|
3月前
|
算法 Java 调度
【多线程面试题二十】、 如何实现互斥锁(mutex)?
这篇文章讨论了在Java中实现互斥锁(mutex)的两种方式:使用`synchronized`关键字进行块结构同步,以及使用`java.util.concurrent.locks.Lock`接口进行非块结构同步,后者提供了更灵活的同步机制和扩展性。
|
4月前
|
C++ 运维
开发与运维编译问题之在C++中在使用std::mutex后能自动释放锁如何解决
开发与运维编译问题之在C++中在使用std::mutex后能自动释放锁如何解决
68 2
|
6月前
|
安全 算法 Linux
【Linux 系统】多线程(线程控制、线程互斥与同步、互斥量与条件变量)-- 详解(下)
【Linux 系统】多线程(线程控制、线程互斥与同步、互斥量与条件变量)-- 详解(下)
|
6月前
|
存储 Linux 程序员
【Linux 系统】多线程(线程控制、线程互斥与同步、互斥量与条件变量)-- 详解(中)
【Linux 系统】多线程(线程控制、线程互斥与同步、互斥量与条件变量)-- 详解(中)
|
6月前
|
缓存 Linux 调度
【Linux 系统】多线程(线程控制、线程互斥与同步、互斥量与条件变量)-- 详解(上)
【Linux 系统】多线程(线程控制、线程互斥与同步、互斥量与条件变量)-- 详解(上)
|
6月前
|
算法 安全 C++
【C++入门到精通】互斥锁 (Mutex) C++11 [ C++入门 ]
【C++入门到精通】互斥锁 (Mutex) C++11 [ C++入门 ]
55 0
|
6月前
|
安全 C++ 开发者
【C++多线程同步】C++多线程同步和互斥的关键:std::mutex和相关类的全面使用教程与深度解析
【C++多线程同步】C++多线程同步和互斥的关键:std::mutex和相关类的全面使用教程与深度解析
90 0
|
Java 调度 C++
C++并发与多线程(五)互斥量,atomic、与线程池(下)
C++并发与多线程(五)互斥量,atomic、与线程池(下)
103 0
|
6月前
|
前端开发 安全 C++
c++11线程、互斥量、条件变量等
c++11线程、互斥量、条件变量等
C++11/14/17中提供的mutex系列区别
C++11/14/17中提供的mutex系列类型如下: