cen's blog cen's blog
首页
  • 编程文章

    • markdown使用
  • 学习笔记

    • 《JavaScript教程》
    • C++学习
    • C++数据结构
    • MySQL
    • Linux
  • 高中时代
  • 工作日常
  • CLion
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 分类
  • 标签
  • 归档
关于
GitHub (opens new window)

cen

十年饮冰,难凉热血
首页
  • 编程文章

    • markdown使用
  • 学习笔记

    • 《JavaScript教程》
    • C++学习
    • C++数据结构
    • MySQL
    • Linux
  • 高中时代
  • 工作日常
  • CLion
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 分类
  • 标签
  • 归档
关于
GitHub (opens new window)
  • Linux环境搭建
  • 基本指令
  • 权限
  • Linux基础开发工具
  • 进程概念
  • 进程控制
  • 基础IO流
  • 动态库和静态库
  • 进程通信
  • 进程信号
  • 多线程
  • 线程安全
    • 线程互斥
      • 初始化互斥量
      • 互斥量的加锁和解锁
      • 互斥量的销毁
    • 互斥量实现原理
  • Linux
cen
2025-05-21
目录

线程安全

# 线程互斥

  • 临界资源:多线程执行流安全访问的共享的资源
  • 临界区:每个线程内部,访问临界资源的代码
  • 互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用
  • 原子性:对一个资源进行访问的时候,要么不做、要么做完,并且对资源进行的操作,只用一条汇编即可完成

例如,下面的模拟抢票程序:

#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

运行程序结果如下:

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

我们发现了票数会重复并且出现了负数,原因如下:

  • if 语句判断条件为真以后,代码可以并发的切换到其他线程
  • usleep 这个模拟漫长业务的过程,在这个漫长的业务过程中,可能有很多个线程会进入该代码段
  • --ticket 操作本身就不是一个原子操作,而是由3条汇编语言构成的:
  1. load :将共享变量ticket从内存加载到寄存器中
  2. update : 更新寄存器里面的值,执行-1操作
  3. store :将新值,从寄存器写回共享变量ticket的内存地址

为了解决以上问题,就要做到以下三点:

  • 代码必须要有互斥行为:当代码进入临界区执行时,不允许其他线程进入该临界区。
  • 如果多个线程同时要求执行临界区的代码,并且临界区没有线程在执行,那么只能允许一个线程进入该临界区。
  • 如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区。

Linux中,提供的方式就是(互斥量)锁

#

# 初始化互斥量

  1. 静态分配
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
  1. 动态分配
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

调用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

# 互斥量的销毁

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

上次更新: 2025/05/23, 14:23:42
多线程

← 多线程

最近更新
01
cmake教程
05-08
02
项目
05-07
03
高中时代
05-07
更多文章>
Theme by Vdoing | Copyright © 2024-2025 京ICP备2020044002号-3 京公网安备11010502056119号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式