线程安全
# 线程互斥
- 临界资源:多线程执行流安全访问的共享的资源
- 临界区:每个线程内部,访问临界资源的代码
- 互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用
- 原子性:对一个资源进行访问的时候,要么不做、要么做完,并且对资源进行的操作,只用一条汇编即可完成
例如,下面的模拟抢票程序:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
int ticket = 100;
void *route(void *arg) {
char *id = (char *)arg;
while (1) {
if (ticket > 0) {
usleep(1000);
printf("%s sells ticket:%d\n", id, ticket);
ticket--;
}
else {
break;
}
}
return nullptr;
}
int main(void) {
pthread_t t1, t2, t3, t4;
pthread_create(&t1, NULL, route, (void*)"thread 1");
pthread_create(&t2, NULL, route, (void*)"thread 2");
pthread_create(&t3, NULL, route, (void*)"thread 3");
pthread_create(&t4, NULL, route, (void*)"thread 4");
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_join(t3, NULL);
pthread_join(t3, NULL);
pthread_join(t4, NULL);
}
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
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
运行程序结果如下:
thread 2 sells ticket:100
thread 3 sells ticket:99
thread 4 sells ticket:98
thread 1 sells ticket:100
...
thread 4 sells ticket:3
thread 3 sells ticket:2
thread 2 sells ticket:1
thread 4 sells ticket:0
thread 3 sells ticket:-1
thread 1 sells ticket:-2
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
我们发现了票数会重复并且出现了负数,原因如下:
- if 语句判断条件为真以后,代码可以并发的切换到其他线程
- usleep 这个模拟漫长业务的过程,在这个漫长的业务过程中,可能有很多个线程会进入该代码段
- --ticket 操作本身就不是一个原子操作,而是由3条汇编语言构成的:
- load :将共享变量ticket从内存加载到寄存器中
- update : 更新寄存器里面的值,执行-1操作
- store :将新值,从寄存器写回共享变量ticket的内存地址
为了解决以上问题,就要做到以下三点:
- 代码必须要有互斥行为:当代码进入临界区执行时,不允许其他线程进入该临界区。
- 如果多个线程同时要求执行临界区的代码,并且临界区没有线程在执行,那么只能允许一个线程进入该临界区。
- 如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区。
Linux中,提供的方式就是(互斥量)锁
#
# 初始化互斥量
- 静态分配
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
1
#include <pthread.h>
#include <unistd.h>
#include <vector>
#include <iostream>
#include "Mutex.hpp"
using namespace std;
// 没有分号哦
#define UserNum 4
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
// 共享资源:
int tickets = 1000;
// 抢票函数:
void* thread_run(void* args) {
char* threadname = (char*) args;
while (true) {
pthread_mutex_lock(&lock);
usleep(10000);
if(tickets > 0) {
cout << threadname << "正在抢票:" << tickets << endl;
tickets--;
// 解锁
pthread_mutex_unlock(&lock);
} else {
pthread_mutex_unlock(&lock);
break;
}
usleep(1000);
}
return nullptr;
}
int main() {
pthread_mutex_t lock;
vector<pthread_t> pids(UserNum);
pthread_mutex_init(&lock, nullptr);
for(int i = 0; i < UserNum; i++) {
char buffer[64];
snprintf(buffer, sizeof buffer, "thread-%d", i + 1);
pthread_create(&pids[i], nullptr, thread_run, buffer);
}
for(auto& pid : pids) {
pthread_join(pid, nullptr);
}
pthread_mutex_destroy(&lock);
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
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
- 动态分配
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
1
# 互斥量的加锁和解锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
// 返回值:成功返回0,失败返回错误号
1
2
3
2
3
调用pthread_mutex_lock时,可能会遇到以下情况:
- 互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功。
- 发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,那么pthread_mutex_lock调用会陷入阻塞(执行流被挂起),等待互斥量解锁。
将模拟抢票程序优化,加上互斥锁:
#include <pthread.h>
#include <unistd.h>
#include <vector>
#include <iostream>
using namespace std;
// 没有分号哦
#define UserNum 4
class ThreadData {
public:
ThreadData(const string& name, pthread_mutex_t* _mutex) : threadname(name), mutex(_mutex) {}
~ThreadData() {}
public:
string threadname;
pthread_mutex_t* mutex;
};
// 共享资源:
int tickets = 1000;
// 抢票函数:
void* thread_run(void* args) {
ThreadData* td = static_cast<ThreadData*>(args);
while (true) {
pthread_mutex_lock(td->mutex);
usleep(10000);
if(tickets > 0) {
cout << td->threadname << "正在抢票:" << tickets << endl;
tickets--;
// 解锁
pthread_mutex_unlock(td->mutex);
} else {
pthread_mutex_unlock(td->mutex);
break;
}
usleep(1000);
}
return nullptr;
}
int main() {
pthread_mutex_t lock;
vector<pthread_t> pids(UserNum);
pthread_mutex_init(&lock, nullptr);
for(int i = 0; i < UserNum; i++) {
char buffer[64];
snprintf(buffer, sizeof buffer, "thread-%d", i + 1);
ThreadData* td = new ThreadData(buffer, &lock);
pthread_create(&pids[i], nullptr, thread_run, td);
}
for(auto& pid : pids) {
pthread_join(pid, nullptr);
}
pthread_mutex_destroy(&lock);
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
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
# 互斥量的销毁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
1
销毁互斥量需要注意:
- 使用 PTHREAD_ MUTEX_ INITIALIZER 初始化的互斥量不需要销毁
- 不能销毁一个已经加锁的互斥量
- 已经销毁的互斥量,要确保后面不会有线程再尝试加锁
# 互斥量实现原理
lock:
movb $0,%al
xchgb al mutex
if(a1寄存器的内容 > 0) {
return 0;
}
else
挂起等待;
goto lock;
unlock :
movb $1,mutex
唤醒等待Mutex的线程;
return 0;
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
上次更新: 2025/05/23, 14:23:42