Drunkmars's Blog

线程同步与线程互斥

字数统计: 1.8k阅读时长: 7 min
2021/07/17 Share

探究一下线程同步和线程互斥的代码实现,记录一下自己的学习过程。

线程同步

在一般情况下,创建一个线程是不能提高程序的执行效率的,所以要创建多个线程。但是多个线程同时运行的时候可能调用线程函数,在多个线程同时对同一个内存地址进行写入,由于CPU时间调度上的问题,写入数据会被多次的覆盖,所以就要使线程同步。

同步就是协同步调,按预定的先后次序进行运行。如:你说完,我再说。

“同”字从字面上容易理解为一起动作

其实不是,“同”字应是指协同、协助、互相配合。

如进程、线程同步,可理解为进程或线程A和B一块配合,A执行到一定程度时要依靠B的某个结果,于是停下来,示意B运行;B依言执行,再将结果给A;A再继续操作。

所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回,同时其它线程也不能调用这个方法。按照这个定义,其实绝大多数函数都是同步调用(例如sin, isdigit等)。但是一般而言,我们在说同步、异步的时候,特指那些需要其他部件协作或者需要一定时间完成的任务。例如Window API函数SendMessage。该函数发送一个消息给某个窗口,在对方处理完消息之前,这个函数不返回。当对方处理完毕以后,该函数才把消息处理函数所返回的LRESULT值返回给调用者。

在多线程编程里面,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何时刻,最多有一个线程访问,以保证数据的完整性。

线程互斥

指在某一时刻指允许一个进程运行其中的程序片,具有排他性和唯一性。

对于线程A和线程B来讲,在同一时刻,只允许一个线程对临界资源进行操作,即当A进入临界区对资源操作时,B就必须等待;当A执行完,退出临界区后,B才能对临界资源进行操作。

代码实现

要求

功能描述:

1、在资源文本框输入任意个数量的字母,并点击开始按钮

2、线程P逐个取出资源文本框中的字母,存储到下面缓冲区的文本框中

3、有四个C线程,从缓冲区中读取字母,读取后将缓冲区置”0”,并讲读取的字母追加到自己的文本框中

大致思路

使用临界区/互斥体、信号量实现

创建1个主线程、4个子线程,主线程和子线程同步,5个线程分别互斥

主线程执行完成后使用ReleaseSemaphore发出信息让子线程执行,子线程执行完成后使用ReleaseSemaphore发出信息让主线程执行

踩的坑

1.注意进入和退出临界区的代码所放的位置,如果进入临界区的位置不对就会导致阻塞

error1

2.注意strcmp()这个函数如果与0进行比较的时候一定要以字符串的形式存在,否则会报错0xC0005。因为两个函数里面都有这个strcmp(),导致报错0xC0005的时候单步去调花了很多时间

error2

3.这里在把数据从缓冲区拿到下面的时候,注意这个地方需要使用到一个文本框数组用来存放,本意是在把数据从缓冲区拿走之后把缓冲区置0,这里如果直接使用lpParameter,就会导致下面的文本框置0

error3

代码实现

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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
// win32 synchronization.cpp : Defines the entry point for the application.
//


#include "stdafx.h"

HWND hEdit_RC,hEdit_B1,hEdit_B2,hEdit_C1,hEdit_C2,hEdit_C3,hEdit_C4; //文本框句柄

HANDLE hThread[5]; //线程数组

CRITICAL_SECTION g_Buffer_CS; //互斥体:缓冲区

HANDLE hSemaphoreEmpty; //信号量:缓冲区有空闲

HANDLE hSemaphoreFull; //信号量:缓冲区有资源

HWND hEditBuffer[3]; //缓冲区文本框数组

HWND hEditCustomer[4]; //消费者文本框数组

DWORD WINAPI ThreadProduct(LPVOID lpParameter)
{
TCHAR szBuffer[256];
TCHAR szTemp[2];
TCHAR szTemp2[2] = { 0 };
DWORD dwLength;
memset(szBuffer, 0, 256);

GetWindowText(hEdit_RC,szBuffer,256);
dwLength = strlen(szBuffer);

if(dwLength == 0)
return -1;
for(DWORD i = 0;i< dwLength ; i++)
{
//等待缓冲区可写资源的信息
WaitForSingleObject(hSemaphoreEmpty, INFINITE);

//寻找可写缓冲区
for(DWORD k = 0; k< 2; k++)
{
memset(szTemp, 0, 2);
EnterCriticalSection(&g_Buffer_CS); //进入临界区

GetWindowText(hEditBuffer[k], szTemp, 2);

if(!strcmp(szTemp, "0"))
{
//TCHAR szT[2] = {0};
memcpy(szTemp2, &szBuffer[i], 1);
SetWindowText(hEditBuffer[k], szTemp2);
LeaveCriticalSection(&g_Buffer_CS); //离开临界区

break;
}

LeaveCriticalSection(&g_Buffer_CS); //当szTemp = 0时,离开缓冲区
}

//发出缓冲区有资源的信息
Sleep(1000);
ReleaseSemaphore(hSemaphoreFull, 1, NULL);
}

return 0;
}
DWORD WINAPI ThreadCustomer(LPVOID lpParameter)
{
TCHAR szTemp[2];
TCHAR szBuffer[256];
TCHAR szNewBuffer[256];
DWORD dwExitCode;

while(1)
{
//等待缓冲区有资源
dwExitCode = WaitForSingleObject(hSemaphoreFull, 10000);

if(dwExitCode == 0x102)
return -1;

//寻找可写资源的缓冲区
EnterCriticalSection(&g_Buffer_CS); //进入临界区

for(DWORD k = 0; k < 2; k++)
{
memset(szTemp, 0, 2);
GetWindowText(hEditBuffer[k], szTemp, 2);

if(strcmp(szTemp, "0"))
{
//存储到文本框
memset(szBuffer, 0, 256);

GetWindowText((HWND)lpParameter, szBuffer, 256);
sprintf(szNewBuffer,"%s->%s", szBuffer, szTemp);
SetWindowText((HWND)lpParameter, szNewBuffer);

Sleep(500);
SetWindowText(hEditBuffer[k], "0");
break;
}
}
LeaveCriticalSection(&g_Buffer_CS); //离开临界区

//发出缓冲区已空的信息
Sleep(1000);
ReleaseSemaphore(hSemaphoreEmpty, 1, NULL);
}
return 0;
}

DWORD WINAPI ThreadMain(LPVOID lpParameter)
{
//创建信号量
hSemaphoreEmpty = CreateSemaphore(NULL, 2, 2,NULL); //缓冲区为空时允许两个线程运行
hSemaphoreFull = CreateSemaphore(NULL, 0, 2,NULL); //缓冲区为空时不允许线程运行

//创建互斥体
InitializeCriticalSection(&g_Buffer_CS);

//创建线程

hThread[0] = ::CreateThread(NULL, 0, ThreadProduct,NULL, 0, NULL);
hThread[1] = ::CreateThread(NULL, 0, ThreadCustomer,hEdit_C1, 0, NULL);
hThread[2] = ::CreateThread(NULL, 0, ThreadCustomer,hEdit_C2, 0, NULL);
hThread[3] = ::CreateThread(NULL, 0, ThreadCustomer,hEdit_C3, 0, NULL);
hThread[4] = ::CreateThread(NULL, 0, ThreadCustomer,hEdit_C4, 0, NULL);

//等待线程结束

::WaitForMultipleObjects(5, hThread, TRUE, INFINITE); //等待所有线程结束
::CloseHandle(hSemaphoreEmpty); //关闭对象句柄
::CloseHandle(hSemaphoreFull); //关闭对象句柄
::DeleteCriticalSection(&g_Buffer_CS); //删除临界区

return 0;
}
BOOL CALLBACK MainDlgProc(HWND hDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
BOOL bRet = FALSE;

switch(uMsg)
{
case WM_CLOSE:
{
EndDialog(hDlg,0);
break;
}
case WM_INITDIALOG:
{
hEdit_RC = GetDlgItem(hDlg,IDC_EDIT1);
hEdit_B1 = GetDlgItem(hDlg,IDC_EDIT2);
hEdit_B2 = GetDlgItem(hDlg,IDC_EDIT3);

hEditBuffer[0] = hEdit_B1;
hEditBuffer[1] = hEdit_B2;

hEdit_C1 = GetDlgItem(hDlg,IDC_EDIT4);
hEdit_C2 = GetDlgItem(hDlg,IDC_EDIT5);
hEdit_C3 = GetDlgItem(hDlg,IDC_EDIT6);
hEdit_C4 = GetDlgItem(hDlg,IDC_EDIT7);

hEditCustomer[0] = hEdit_C1;
hEditCustomer[1] = hEdit_C2;
hEditCustomer[2] = hEdit_C3;
hEditCustomer[3] = hEdit_C4;

SetWindowText(hEdit_RC,"0");
SetWindowText(hEdit_B1,"0");
SetWindowText(hEdit_B2,"0");
SetWindowText(hEdit_C1,"");
SetWindowText(hEdit_C2,"");
SetWindowText(hEdit_C3,"");
SetWindowText(hEdit_C4,"");

break;
}
case WM_COMMAND:

switch (LOWORD (wParam))
{
case IDC_BUTTON_BEGIN:
{

CreateThread(NULL, 0, ThreadMain,NULL, 0, NULL);

return TRUE;
}
}

break ;
}

return bRet;
}
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
// TODO: Place code here.

DialogBox(hInstance,MAKEINTRESOURCE(IDD_DIALOG_MAIN),NULL,MainDlgProc);

return 0;
}

实现效果

这里在第一个缓冲区->第二个缓冲区的过程中加了一个Sleep(),所以第二个缓冲区往下读取的时候会比第一个缓冲区稍慢

eat letter

CATALOG
  1. 1. 线程同步
  2. 2. 线程互斥
  3. 3. 代码实现
    1. 3.1. 要求
    2. 3.2. 大致思路
    3. 3.3. 踩的坑
    4. 3.4. 代码实现
    5. 3.5. 实现效果