linux学习笔记(21)线程同步——互斥锁

线程同步

线程同步指的是当一个线程在对某个临界资源进行操作时,其他线程都不可以对这个资源进行操作,直到该线程完成操作,其他线程才能操作,也就是协同步调,让线程按预定的先后次序进行运行。线程同步的方法有四种:
互斥锁、信号量、条件变量、读写锁

互斥锁

用于保护临界区,确保同一时间只有一个线程可以访问共享资源。

互斥锁的创建和初始化

方法一:静态初始化(推荐)

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

方法二:动态初始化

cpp 复制代码
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);  // 第二个参数为属性,NULL表示默认
// 使用后必须销毁
pthread_mutex_destroy(&mutex);

互斥锁函数总结表格(带实例)

|------------|--------------------------------------------------------------------------------------------------------------------------|---------------|-------------------|
| 函数类别 | 函数原型 | 作用 | 返回值 |
| 初始化 | pthread_mutex_init(&mutex, NULL); pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; | 初始化互斥锁 静态初始化 | 0=成功 |
| 销毁 | pthread_mutex_destroy(&mutex); | 销毁互斥锁,释放资源 | 0=成功 |
| 阻塞加锁 | pthread_mutex_lock(&mutex); // 临界区代码 pthread_mutex_unlock(&mutex); | 阻塞式加锁,等待直到获取锁 | 0=成功 |
| 非阻塞加锁 | if (pthread_mutex_trylock(&mutex) == 0) { // 成功获取锁 pthread_mutex_unlock(&mutex); } else { // 锁正忙 } | 尝试加锁,立即返回不阻塞 | 0=成功 EBUSY=锁忙 |
| 超时加锁 | struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); ts.tv_sec += 2; // 2秒超时 pthread_mutex_timedlock(&mutex, &ts); | 在指定时间内等待锁 | 0=成功 ETIMEDOUT=超时 |
| 解锁 | pthread_mutex_unlock(&mutex); | 释放互斥锁 | 0=成功 |
| 属性初始化 | pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_destroy(&attr); | 初始化/销毁属性对象 | 0=成功 |
| 设置锁类型 | pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); pthread_mutex_init(&mutex, &attr); | 设置递归锁、错误检查锁等 | 0=成功 |
| 获取锁类型 | int type; pthread_mutexattr_gettype(&attr, &type); | 获取当前锁类型 | 0=成功 |
| 设置进程共享 | pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); | 设置进程间共享 | 0=成功 |

cpp 复制代码
#include <stdio.h>
#include <pthread.h>
// 全局变量和锁
int counter = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void* increment(void* arg) {
    for (int i = 0; i < 100000; i++) {
        pthread_mutex_lock(&mutex);    // 加锁
        counter++;                     // 临界区
        pthread_mutex_unlock(&mutex);  // 解锁
    }
    return NULL;
}
int main() {
    pthread_t t1, t2;    
    pthread_create(&t1, NULL, increment, NULL);
    pthread_create(&t2, NULL, increment, NULL);
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    printf("最终结果: %d (应该是200000)\n", counter);
    return 0;
}

互斥锁的两种类型

不可重入锁(默认的 pthread_mutex_t**)**

  • 特点 :同一线程不能对同一把锁加锁两次
  • 后果 :第二次调用 pthread_mutex_lock 会死锁(自己把自己卡住)。
cpp 复制代码
#include <pthread.h>
#include <stdio.h>

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

void foo() {
    pthread_mutex_lock(&lock);   // 第一次加锁 OK
    printf("第一次加锁成功\n");

    pthread_mutex_lock(&lock);   // 第二次加锁 -> 死锁!
    printf("第二次加锁成功\n");  // 这行永远执行不到

    pthread_mutex_unlock(&lock);
    pthread_mutex_unlock(&lock);
}

int main() {
    foo();
    return 0;
}

打印 "第一次加锁成功" 后程序卡死,因为第二次加锁时,线程已经持有锁,但又去申请同一把锁,就永远等不到自己释放锁。

可重入锁( PTHREAD_MUTEX_RECURSIVE**)**

  • 特点:同一线程可以多次加锁同一把锁,不会死锁。
  • 条件:加锁多少次,就要解锁多少次,否则其他线程无法获得锁。
cpp 复制代码
pthread_mutexattr_t attr;
pthread_mutex_t lock;

pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&lock, &attr);
cpp 复制代码
#include <pthread.h>
#include <stdio.h>
pthread_mutex_t lock;
void foo() {
    pthread_mutex_lock(&lock);
    printf("第一次加锁\n");
    pthread_mutex_lock(&lock);
    printf("第二次加锁\n");
    pthread_mutex_unlock(&lock);
    pthread_mutex_unlock(&lock);
}
int main() {
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
    pthread_mutex_init(&lock, &attr);
    foo();
    pthread_mutex_destroy(&lock);
    pthread_mutexattr_destroy(&attr);
    return 0;
}
第一次加锁
第二次加锁

程序正常结束,因为可重入锁允许同一线程多次加锁。

死锁的原因

不可重入锁死锁的原因是:

  • 互斥锁的设计目的就是独占------ 同一把锁在释放前只能被一个线程持有。
  • 如果同一线程再次加锁,就相当于 "自己等自己释放锁",永远等不到,于是死锁。

什么时候会不小心锁两次?

  • 递归函数调用(外层已经加锁,内层又加锁)。
  • 不同函数之间调用,没有意识到上一层已经加锁。
    死锁之后,再 unlock 两次也没用
    线程被挂起,后面的两个 unlock 根本没有机会运行,所以死锁无法解除。
    死锁一旦发生,单靠用户代码是解不开的,因为线程已经没法继续执行了。常见的处理方式是:
  1. 预防:在设计代码时避免出现死锁(比如避免同一线程重复加锁、保证加锁顺序一致等)。
  2. 调试 :用工具(如 gdb、pstack、htop)确认死锁位置,然后修改代码逻辑。
  3. 强制结束 :在外部 kill 掉卡死的进程(这是最后的无奈之举)。
    如果你用的是可重入锁 (PTHREAD_MUTEX_RECURSIVE):
  • 同一线程可以多次 lock,不会死锁。
  • 但你必须 unlock 同样的次数,其他线程才能获得这把锁。
  • 如果 unlock****次数多于 lock****次数,会导致未定义行为(可能程序崩溃)
相关推荐
泽虞5 小时前
《Qt应用开发》笔记
linux·开发语言·c++·笔记·qt
『往事』&白驹过隙;6 小时前
浅谈内存DDR——DDR4性能优化技术
科技·物联网·学习·性能优化·内存·ddr
一只积极向上的小咸鱼6 小时前
Windows中通过wsl运行Ubuntu
linux·运维·ubuntu
明明真系叻7 小时前
《量子计算》学习笔记:量子计算的基本定义(续)
笔记·学习·量子计算
ayaya_mana7 小时前
Linux环境下Node.js任意版本安装与pnpm、yarn包管理
linux·node.js·vim
乌龙玛奇朵5197 小时前
Finalshell建立连接
linux
Maple_land7 小时前
Linux进程第八讲——进程状态全景解析(二):从阻塞到消亡的完整生命周期
linux·运维·服务器·c++·centos
嵌入式分享7 小时前
嵌入式分享#41:RK3576改UART波特率【精简版】
linux·嵌入式硬件·ubuntu·嵌入式
爱吃生蚝的于勒7 小时前
【Linux】零基础学会Linux之权限
linux·运维·服务器·数据结构·git·算法·github