临界区

线程锁相关API

每个线程都有自己的栈,而局部变量是储存在栈中的,这就意味着每个线程都有一分自己的“局部变量”,如果线程仅仅使用“局部变量”那么就不存在线程安全问题。

线程锁仅仅只是防止同一进程间多线程访问同一个临界资源(一般是全局变量)

创建全局变量

1
CRITICAL_SECTION cs;

初始化全局变量

1
InitializeCriticalSection(&cs);

实现临界区

1
2
3
EnterCriticalSection(&cs);
//使用临界资源
LeaveCriticalSection(&cs);

使用C语言创建线程锁

注意,使用线程锁时要将需要使用的全局资源包含在临界区,此例中要确保count处于临界区

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

#include<stdio.h>
#include<Windows.h>
#include<stdlib.h>

int count = 10;

//令牌
CRITICAL_SECTION cs;

DWORD WINAPI ThreadProc(_In_ LPVOID lpParameter) {

/*
确保count处于临界区*/
EnterCriticalSection(&cs);
while (count > 0) {
printf("proc1 %d\n", count);
Sleep(200);
count--;
}
LeaveCriticalSection(&cs);

return 0;

}

DWORD WINAPI ThreadProc1(_In_ LPVOID lpParameter) {

EnterCriticalSection(&cs);
while (count > 0) {
printf("proc2 %d\n", count);
Sleep(200);
count--;
}
LeaveCriticalSection(&cs);

return 0;

}

int main() {

InitializeCriticalSection(&cs);


HANDLE hThread[2];

hThread[0] = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
hThread[1] = CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL);

WaitForMultipleObjects(2, hThread, TRUE, INFINITE);

CloseHandle(hThread[0]);
CloseHandle(hThread[1]);
system("pause");
}



互斥体

如果多个进程需要访问同一内核级的临界资源可以使用互斥体,线程锁只能控制同一个进程中的多个线程同时访问一个临界资源。

函数原形

1
2
3
4
5
6
HANDLE CreateMutexA(
//安全描述符
LPSECURITY_ATTRIBUTES lpMutexAttributes,
BOOL bInitialOwner,
LPCSTR lpName
);

关于返回值

如果执行成功则返回互斥体的句柄,否则返回NULL,这个返回值并不能判断互斥体是否已被占用,可以调用GetLastError(),如果结果为ERROR_ALREADY_EXISTS则互斥体已被占用
官方文档:
If the function succeeds, the return value is a handle to the newly created mutex object.

If the function fails, the return value is NULL. To get extended error information, call GetLastError.

If the mutex is a named mutex and the object existed before this function call, the return value is a handle to the existing object, and the GetLastError function returns ERROR_ALREADY_EXISTS.


互斥体和线程锁的区别

  1. 线程锁只能用于单个进程间的线程控制

  2. 互斥体可以设定等待超时,但线程锁不能

  3. 线程锁意外终结时,Mutex可以避免无限等待

  4. Mutex效率没有线程锁高


使用C语言创建互斥体

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

#include<stdio.h>
#include<Windows.h>
#include<stdlib.h>



int main() {

HANDLE hMutex = CreateMutex(NULL, FALSE, L"互斥体");

/*
获取令牌*/
//WaitForSingleObject(hMutex, INFINITE);

DWORD dwErr = GetLastError();

//确保CreateMutex执行成功
if (hMutex) {
//互斥体已被占用
if (dwErr == ERROR_ALREADY_EXISTS) {
CloseHandle(hMutex);
return 0;
}
}
else {
printf("Failed to create, program exits");
CloseHandle(hMutex);
return 0;
}

while (true) {
printf("程序执行中\n");
Sleep(1000);
}

system("pause");
}



线程同步

线程同步是指线程之间所具有的一种制约关系,一个线程的执行依赖另一个线程的消息,当它没有得到另一个线程的消息时应等待,直到消息到达才被唤醒。

同步 = 互斥 + 有序

使用互斥实现线程同步

使用互斥实现线程同步会浪费CPU时间

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

#include<stdio.h>
#include<Windows.h>
#include<stdlib.h>

HANDLE hMutex;
int max = 10;
int number = 0;

DWORD WINAPI threadProduct(_In_ LPVOID lpParameter) {
for (int i = 0; i < max; i++) {
//获取令牌 互斥的访问缓冲区
WaitForSingleObject(hMutex, INFINITE);
if (number == 0) {
number = 1;
DWORD id = GetCurrentThreadId();
printf("生产者%d将%d放入缓冲区\n", id, number);
}
else {
//平衡i
printf("-----------------");
i--;
}
ReleaseMutex(hMutex);
}

return 0;
}

DWORD WINAPI threadConsumer(_In_ LPVOID lpParameter) {
for (int i = 0; i < max; i++) {
//获取令牌
WaitForSingleObject(hMutex, INFINITE);
if (number == 1) {
number = 0;
DWORD id = GetCurrentThreadId();
printf("消费者%d将%d放入缓冲区\n", id, number);
}
else {
printf("-----------------");
i--;
}
ReleaseMutex(hMutex);
}

return 0;
}


int main() {

HANDLE hTread[2];

hMutex = CreateMutex(NULL, FALSE, NULL);

hTread[0] = CreateThread(NULL, 0, threadProduct, NULL, 0, NULL);
hTread[1] = CreateThread(NULL, 0, threadConsumer, NULL, 0, NULL);

WaitForMultipleObjects(2, hTread, TRUE, INFINITE);

CloseHandle(hTread[0]);
CloseHandle(hTread[1]);
CloseHandle(hMutex);

system("pause");
}


使用事件实现线程同步

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

#include<stdio.h>
#include<Windows.h>
#include<stdlib.h>

HANDLE hMutex;
HANDLE hSet;
HANDLE hClear;
int max = 10;
int number = 0;

DWORD WINAPI threadProduct(_In_ LPVOID lpParameter) {

for (int i = 0; i < max; i++) {
/*
WaitForSingleObject函数检查指定对象的当前状态。
如果对象的状态为无信号状态,则调用线程进入等待状态,
等待完毕后会将事件对象设置为无信号*/
WaitForSingleObject(hSet, INFINITE);
number = 1;
DWORD id = GetCurrentThreadId();
printf("生产者%d将%d放入缓冲区\n", id, number);

//设置Consumer线程的事件为有信号
SetEvent(hClear);
}

return 0;
}

DWORD WINAPI threadConsumer(_In_ LPVOID lpParameter) {
for (int i = 0; i < max; i++) {
//第一次执行时hClear为无信号,会进入阻塞状态
//等待完毕后Clear事件为无信号
WaitForSingleObject(hClear, INFINITE);
number = 0;
DWORD id = GetCurrentThreadId();
printf("----消费者%d将%d放入缓冲区\n", id, number);
SetEvent(hSet);
}

return 0;
}


int main() {

HANDLE hTread[2];

/*
创建一个自动重置的事件,初始有信号
当第二个参数为FALSE时,在单个等待线程被释放后,系统会自动将事件状态重置为无信号状态*/
hSet = CreateEvent(NULL, FALSE, TRUE, NULL);

//初始为无信号
hClear = CreateEvent(NULL, FALSE, FALSE, NULL);

hTread[0] = CreateThread(NULL, 0, threadProduct, NULL, 0, NULL);
hTread[1] = CreateThread(NULL, 0, threadConsumer, NULL, 0, NULL);


WaitForMultipleObjects(2, hTread, TRUE, INFINITE);

CloseHandle(hTread[0]);
CloseHandle(hTread[1]);
CloseHandle(hSet);
CloseHandle(hClear);

system("pause");
}