0%

WindowsAPI - 线程同步

最近在学习 Win32编程,所以顺便将每日所学记录下来,一方面为了巩固学习的知识,另一方面也为同样在学习Win32开发的童鞋们提供一份参考。

本系列博文均根据学习《WindowsAPI开发详解》一书总结而来;

运行环境:

  • 操作系统: Windows 10家庭版
  • 编译器:Visual Studio 2013

线程同步

同步原理

在多线程中线程同步很有意义,在线程同步过程中,需要先定义一个同步对象,同步对象一般具有两种状态:标志的和未标志的。

同步对象

最长用到的同步对象包括:Event、Mutex、Semaphore、Waitable;

等待函数

最常用于的就是WaitForSingleObject、WaitForMultipleObjects

WaitForSingleObject

1
DWORD WaitForSingleObject( HANDLE hHandle, DWORD dwMilliseconds ); 

WaitForMultipleObjects

1
2
3
4
DWORD WaitForMultipleObjects(DWORD nCount, 
CONST HANDLE *lpHandles,
BOOL fWaitAll,
DWORD dwMilliseconds );

同步对象

事件Event

CreateEvent

事件在线程同步中是最长使用的一种同步对象。

1
2
3
4
HANDLE CreateEvent( LPSECURITY_ATTRIBUTES lpEventAttributes, 		//安全描述符
BOOL bManualReset, //是否手工重置
BOOL bInitialState, //是否置位
LPTSTR lpName); //事件名

SetEvent

将事件对象设置为标记;

1
BOOL SetEvent(HANDLE hEvent ); 

ResetEvent

重置标记,如果事件是手工重置,那么需要该函数来重置事件

1
BOOL ResetEvent( HANDLE hEvent ); 

OpenEvent

从事件名中得到事件句柄

1
2
3
HANDLE OpenEvent(  DWORD dwDesiredAccess,  // access
BOOL bInheritHandle, // inheritance option
LPCTSTR lpName // object name);
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
#include <Windows.h>
#include <tchar.h>
#include <stdio.h>

#define NUMTHREADS 3
#define BUFFER_SIZE 16
#define FOR_TIMES 5

HANDLE hEvent;
BYTE lpSharedBuffer[16] = { 0 };


void UseEvent();
DWORD WINAPI ThreadProc(LPVOID lpParam);


VOID UseEvent()
{
HANDLE hThread;
hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); //FALSE为有信号
if (hEvent == NULL)
{
printf("CreateEvent Error = %d\n", GetLastError());
return;
}
hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
if (hThread == NULL)
{
printf("CreateThread Error = %d\n", GetLastError());
return;
}
Sleep(2000);
CopyMemory(lpSharedBuffer, "EVENT", strlen("EVENT") + 1);
SetEvent(hEvent); //设置为通知
}

DWORD WINAPI ThreadProc(LPVOID lpParam)
{
DWORD dwWaitResult;
dwWaitResult = WaitForSingleObject(hEvent, INFINITE); //等待事件被重置

if (dwWaitResult != WAIT_OBJECT_0)
{
printf("Wait Error : %d\n", GetLastError());
return 0;
}
printf((CONST char *)lpSharedBuffer); //读共享内存
if (!ResetEvent(hEvent)) //重置事件
{
printf("SetEvent Error:%d\n", GetLastError());
return 1;
}
return 0;
}

int main()
{
UseEvent();
getchar();
return 0;
}

两个线程中使用事件对象:

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
/* 头文件 */
#include <windows.h>
#include <stdio.h>
/* 常量定义 */
#define NUMTHREADS 3
#define BUFFER_SIZE 16
#define FOR_TIMES 5
/* 全局变量 */
HANDLE hWriteEvent[NUMTHREADS]; // 写Event 表示写操作是否完成
HANDLE hReadEvents[NUMTHREADS]; // 读Event 表示读操作是否完成
BYTE lpSharedBuffer[16] = { 0 }; // 共享内存
/* 函数声明 */
void MultiEvents(void);
VOID WriteToBuffer(VOID);
DWORD WINAPI ThreadFunction(LPVOID lpParam);

/*************************************
* int main(void)
* 功能 演示
*
* 参数 未使用
**************************************/
int main()
{
MultiEvents();
getchar();
return 0;
}

/*************************************
* void UseEvents(void)
* 功能 演示Event的使用方法
*
* 参数 未使用
**************************************/
void MultiEvents(void)
{
HANDLE hThread;
DWORD i;
// 创建多个线程,读共享内存,主线程写共享内存。
// 每个线程都有对应的读写同步事件
for (i = 0; i < NUMTHREADS; i++)
{
// 每个线程都有一个Event表示写入操作完成
hWriteEvent[i] = CreateEvent(
NULL, // 默认安全属性
FALSE, // 自动重置
FALSE, // 初始为未置位的
NULL // 未命名
);
// 判断是否创建成功
if (hWriteEvent[i] == NULL)
{
printf("CreateEvent failed (%d)\n", GetLastError());
return;
}
// 每个读线程有一个Event表示读操作已经完成
hReadEvents[i] = CreateEvent(
NULL, // 默认安全属性
FALSE, // 自动重置
FALSE, // 初始化为未置位的
NULL); // 未命名
if (hReadEvents[i] == NULL)
{
printf("CreateEvent failed (%d)\n", GetLastError());
return;
}
// 创建线程
hThread = CreateThread(NULL, 0,
ThreadFunction,
(LPVOID)i, // Event对象句柄作为
0, NULL);
if (hThread == NULL)
{
printf("CreateThread failed (%d)\n", GetLastError());
return;
}
}
WriteToBuffer();
}

/*************************************
* VOID WriteToBuffer(INT iContent)
* 功能 由主线程调用,向共享内存中写入数据
* 等待所有读线程读完后函数返回
*
* 参数 未使用
**************************************/
VOID WriteToBuffer(VOID)
{
DWORD dwWaitResult, j, i;
// 完成 FOR_TIMES 次读写
for (j = 0; j < FOR_TIMES; j++)
{
Sleep(rand() % 100); // 写入需要的时间随机
// 写入共享内存
wsprintf((LPWSTR)lpSharedBuffer, TEXT("shared %d"), j);
// 将线程对应的写Event置为“标志的”,表示写操作完成,
// 其他线程可以开始读
for (i = 0; i<NUMTHREADS; i++)
{
if (!SetEvent(hWriteEvent[i]))
{
printf("SetEvent failed (%d)\n", GetLastError());
return;
}
}
// 等待所有的线程读完,开始下次写入
dwWaitResult = WaitForMultipleObjects(
NUMTHREADS, // Event句柄的个数
hReadEvents, // Event句柄数组
TRUE, // 等到所有的Event都被标志
INFINITE); // 无限等待
// 判断等待结果
if (dwWaitResult != WAIT_OBJECT_0)
{
printf("Wait error: %d\n", GetLastError());
ExitProcess(0);
}
}
}

/*************************************
* DWORD WINAPI ThreadFunction(LPVOID lpParam)
* 功能 线程函数,读共享内存
*
* 参数 LPVOID lpParamt 实际为指向Event句柄的指针
**************************************/
DWORD WINAPI ThreadFunction(LPVOID lpParam)
{
DWORD dwWaitResult;
BYTE lpRead[16];
DWORD j = 0;
DWORD dwThreadIndex = (DWORD)lpParam;
// 完成 FOR_TIMES 次读写
for (; j<FOR_TIMES; j++)
{
// 等待写事件置位,表示数据已经写入
dwWaitResult = WaitForSingleObject(
hWriteEvent[dwThreadIndex], // Event 句柄
INFINITE); // 无限等待
switch (dwWaitResult)
{
case WAIT_OBJECT_0:
Sleep(rand() % 10); // 模拟数据处理所需的时间随机
CopyMemory(lpRead, lpSharedBuffer, 16);
break;
// 发生错误
default:
printf("Wait error: %d\n", GetLastError());
ExitThread(0);
}
// 将读Event置位,表示读操作完成
if (!SetEvent(hReadEvents[dwThreadIndex]))
{
printf("SetEvent failed (%d)\n", GetLastError());
return 0;
}
//打印读到的内容
printf("线程 %u\t第 %d 次读,内容:%ls \r\n",
dwThreadIndex, j, (LPSTR)lpRead);
printf("\n");
}
return 1;
}

互斥对象Mutex

互斥对象具有以下特性:如果互斥对象没有被任何线程拥有,那么它是标记的,如果被一个线程拥有,那么它就是未标记的。

CreateMutex

创建互斥对象

1
2
3
HANDLE CreateMutex( LPSECURITY_ATTRIBUTES lpMutexAttributes, 		//安全描述符
BOOL bInitialOwner, //是否被线程拥有
LPCTSTR lpName ); //互斥体名字

OpenMutex

通过互斥对象名获取对象句柄;

dwDesiredAccess:对互斥量对象访问权限的设置,MUTEX_ALL_ACCESS 请求对互斥体的完全访问,MUTEX_MODIFY_STATE 允许使用 ReleaseMutex 函数,SYNCHRONIZE 允许互斥体对象同步使用;

1
2
3
HANDLE OpenMutex(  DWORD dwDesiredAccess,  // access
BOOL bInheritHandle, // inheritance option
LPCTSTR lpName // object name);

ReleaseMutex

释放互斥对象,一个线程释放了互斥对象后,如果其他进程在等待,则可以获得该互斥对象;

1
BOOL ReleaseMutex( HANDLE hMutex );
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
/* ************************************
*《精通Windows API》
* 示例代码
* Mutex.c
* 7.2.2 演示使用Mutex同步线程
**************************************/

/* 头文件 */
#include <windows.h>
#include <stdio.h>
/* 常量定义 */
#define NUM_THREADS 4
/* 全局变量 */
DWORD dwCounter = 0;
HANDLE hMutex;
/* 函数声明 */
void UseMutex(void);
DWORD WINAPI MutexThread(LPVOID lpParam);

/*************************************
* int main(void)
* 功能 演示
*
* 参数 未使用
**************************************/
int main()
{
UseMutex();
getchar();
}
/*************************************
* void UseMutex(void)
* 功能 演示 Mutex 的使用方法
*
* 参数 未使用
**************************************/
void UseMutex(void)
{
INT i;
HANDLE hThread;

//#ifdef MUTEX
// 创建 Mutex
hMutex = CreateMutex(
NULL, // 默认安全属性
FALSE, // 初始化为未被拥有
NULL); // 未命名
if (hMutex == NULL)
{
printf("CreateMutex error: %d\n", GetLastError());
}
//#endif
// 创建多个线程
for (i = 0; i < NUM_THREADS; i++)
{
hThread = CreateThread(NULL, 0,
MutexThread,
NULL,
0, NULL);
if (hThread == NULL)
{
printf("CreateThread failed (%d)\n", GetLastError());
return;
}
}
Sleep(1000);
}

/*************************************
* DWORD WINAPI MutexThread(LPVOID lpParam)
* 功能 线程函数,读共享内存
*
* 参数 未使用
**************************************/
DWORD WINAPI MutexThread(LPVOID lpParam)
{

//#ifdef MUTEX
DWORD dwWaitResult;
dwWaitResult = WaitForSingleObject(
hMutex, // 句柄
INFINITE); // 无限等待

switch (dwWaitResult)
{
case WAIT_OBJECT_0:
//#endif
// 等待随机长的时间,各个线程等待的时间将不一致
Sleep(rand() % 100);
// 得到互斥对象后 访问共享数据
printf("counter: %d\n", dwCounter);
// 互斥对象保证同一时间只有一个线程在访问dwCounter
dwCounter++;

//#ifdef MUTEX
// 释放 Mutex
if (!ReleaseMutex(hMutex))
{
printf("Release Mutex error: %d\n", GetLastError());
}
break;
default:
printf("Wait error: %d\n", GetLastError());
ExitThread(0);
}
//#endif
return 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
#include <Windows.h>
#include <tchar.h>
#include <stdio.h>

int main()
{
HANDLE g_hMutex = CreateMutex(NULL, TRUE, TEXT("sYstemk1t"));//获取令牌
DWORD dwMutexResults = GetLastError();
//WaitForSingleObject(g_hMutex, INFINITE);
if (g_hMutex)
{
if (ERROR_ALREADY_EXISTS == dwMutexResults)
{
printf("程序已经打开了\n");
Sleep(2000);
return 0;
}
}
else
{
printf("创建失败\n");
return 0;
}
while (1)
{
Sleep(2000);
printf("程序正在运行\n");
}
//ReleaseMutex(g_hMutex); //释放令牌
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <windows.h>
#include <stdio.h>
int main()
{
//创建一个互斥体
HANDLE g_hMutex = CreateMutex(0, FALSE, TEXT("sYstemk1t")); //TRUE 创建出来无信号 FALSE 创建出来有信号

//获取互斥体,等待令牌
WaitForSingleObject(g_hMutex, INFINITE);

for (int i = 0; i < 10; i++)
{
Sleep(1000);
printf("B进程的Y线程:%d \n", i);
}
//释放令牌
ReleaseMutex(g_hMutex);
return 0;
}


信号量

信号量维护了一个计数器,计数器的值可以在0到指定的最大值之间。当一个线程完成了对信号量的等待后,信号量计数器值减少。当一个线程释放信号量时,信号量计数器增加。

CreateSemaphore

创建信号量对象

1
2
3
4
5
HANDLE CreateSemaphore(
LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, //安全属性
LONG lInitialCount, //初始计数值
LONG lMaximumCount, //最大计数值
LPCTSTR lpName ); //信号量命名对象

OpenSemaphore

通过信号量命名,获得信号量对象句柄

1
2
3
HANDLE OpenSemaphore(  DWORD dwDesiredAccess,  // access
BOOL bInheritHandle, // inheritance option
LPCTSTR lpName // object name);

SEMAPHORE_ALL_ACCESS (0x1F0003) 要求对事件对象的完全访问;

SEMAPHORE_MODIFY_STATE (0x0002) 允许使用ReleaseSemaphore函数;

SYNCHRONIZE (0x00100000L)允许同步使用信号机对象。

ReleaseSemaphore

1
2
3
BOOL ReleaseSemaphore(HANDLE hSemaphore, 		//信号量对象的句柄
LONG lReleaseCount, //要增加信号对象的当前计数的量
LPLONG lpPreviousCount ); //指向要接收信号量的上一个计数的变量的指针
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
/* 头文件 */
#include <windows.h>
#include <stdio.h>
/* 常量定义 */
#define NUMTHREADS 4
/* 全局变量 */
HANDLE hSemaphore;
/* 函数声明 */
void UseSemaphore(void);
DWORD WINAPI SemaphoreThread(LPVOID lpParam);

/*************************************
* int main(void)
* 功能 演示
*
* 参数 未使用
**************************************/
int main()
{
UseSemaphore() ;
getchar();
return 0;

}
/*************************************
* DWORD WaitForAllThread(HANDLE hThread[], DWORD dwNumThread)
* 功能 等待指定的线程都结束
*
* 参数 HANDLE hThread[] 需要等待结束的线程句柄数组
* DWORD dwNumThread 线程句柄数组的大小
**************************************/
DWORD WaitForAllThread(HANDLE hThread[], DWORD dwNumThread)
{
// 等待所有线程结束
DWORD dwWaitResult = WaitForMultipleObjects(
dwNumThread,
hThread, // 线程句柄作为等待对象
TRUE,
INFINITE);
switch (dwWaitResult)
{
case WAIT_OBJECT_0:
printf("\nAll thread exit\n");
break;
default:
printf("\nWait error: %u", GetLastError());
}
return 0;

}

/*************************************
* void UseSemaphore(void)
* 功能 演示UseSemaphore的使用方法
*
* 参数 未使用
**************************************/
void UseSemaphore(void)
{
HANDLE hThread[NUMTHREADS];
INT i;
LONG lMax;
CHAR cMax;
// 打印信息获取输入
printf("将创建%d个进程,获得信号量的进程可以向屏幕打印。\n"
"请输入信号量的最大计数1~%d:",NUMTHREADS,NUMTHREADS);
// 获得输入的字符
cMax = getch();
printf("%c\n",cMax);
// 将字符转换为数字
lMax = cMax & 0xF;
if(lMax<0 || lMax>NUMTHREADS)
{
printf("请输入1-%d",NUMTHREADS);
}
// 创建信号量
hSemaphore = CreateSemaphore(
NULL, // 默认安全属性
lMax, // 初始化计数器,用户输入
lMax, // 最大计数,用户输入
NULL); // 未命名
if (hSemaphore == NULL)
{
printf("CreateSemaphore error: %d\n", GetLastError());
}
// 创建多个线程 访问共享资源
for(i = 0; i < NUMTHREADS; i++)
{
hThread[i] = CreateThread(NULL, 0,
SemaphoreThread,
&i, // 进程序号作为参数
0, NULL);
if (hThread[i] == NULL)
{
printf("CreateThread failed (%d)\n", GetLastError());
return;
}
}
// 等待所有线程都执行完成并退出
WaitForAllThread(hThread,NUMTHREADS);
}

/*************************************
* DWORD WINAPI SemaphoreThread(LPVOID lpParam)
* 功能 线程函数,读共享内存
*
* 参数 未使用
**************************************/
DWORD WINAPI SemaphoreThread(LPVOID lpParam)
{
DWORD dwWaitResult;
BYTE lpRead[16];
//DWORD i = *(LPWORD)lpParam; // 线程的编号
DWORD dwPreviousCount;

DWORD j = 0;
// 每个线程都将访问3次受限资源
for(; j<3; j++)
{
// 线程可以在此进行一些操作
// 以暂停随机长的时间模拟真实情况
Sleep(rand()%1000);
// 等待信号量
dwWaitResult = WaitForSingleObject(
hSemaphore, // 信号量句柄
INFINITE); // 无限等待
switch (dwWaitResult)
{
case WAIT_OBJECT_0:
printf("\nProcess %d Gets Semaphore",GetCurrentThreadId());
break;
default:
printf("\nprocess %u Wait error: %u",GetCurrentThreadId(), GetLastError());
}
// 获得信息量后访问受限资源
// 以等待随机长时间模块真实情况
Sleep(rand()%1000);
// 释放信号量
if (!ReleaseSemaphore(
hSemaphore, // 信号量句柄
1, // 释放后计数器减1
&dwPreviousCount) ) // 获得计数
{
printf("\nprocess %u ReleaseSemaphore error: %d", GetCurrentThreadId(), GetLastError());
}
else
{
printf("\nProcess %u release Semaphore, previous count is %u",
GetCurrentThreadId(), dwPreviousCount);
}
}

return 1;
}