学习笔记——线程控制 - 互斥与同步

线程控制 - 互斥与同步

一、 互斥(Mutex)

1.概念

在多线程中对临界资源的排他性访问

  • 临界资源:在多线程中会被多个线程进行读写操作的资源(全局变量、文件、设备等)

  • 排他访问:同一时刻只能有一个线程进行读写操作

2.用途

在多线程中,一个资源同一时刻只能有一个线程访问。

示例问题

复制代码
int A = 0;  // 临界资源

// 线程1
void* th1(void* arg) {
    A++;  // 这不是原子操作!
}

// 线程2  
void* th2(void* arg) {
    A++;
}

A++在汇编中至少需要3步:

  1. 读取A到寄存器

  2. 寄存器值加1

  3. 将结果写回A

如果th1执行了1、2步后切换到th2,就会发生数据一致性问题

解决方案:使用互斥锁

使用步骤

  1. 定义互斥锁

  2. 初始化锁

  3. 加锁

  4. 解锁

  5. 销毁

相关函数

1. 定义互斥锁
复制代码
pthread_mutex_t mutex;
2. 初始化锁
复制代码
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
  • 功能:初始化互斥锁

  • 参数

    • mutex:要初始化的互斥锁

    • attr:初始化属性,一般为NULL(默认锁)

  • 返回值:成功返回0,失败返回非0

3. 加锁
复制代码
int pthread_mutex_lock(pthread_mutex_t *mutex);
  • 功能:给代码加锁

  • 特点

    • 加锁到解锁之间的代码属于原子操作

    • 在加锁期间其他线程不能执行该部分代码

    • 如果锁已被占用,线程会阻塞等待

  • 返回值:成功返回0,失败返回非0

4. 解锁
复制代码
int pthread_mutex_unlock(pthread_mutex_t *mutex);
  • 功能:解锁互斥锁

  • 注意:加锁和解锁一般成对出现

  • 返回值:成功返回0,失败返回非0

5. 销毁
复制代码
int pthread_mutex_destroy(pthread_mutex_t *mutex);
  • 功能:销毁互斥锁

  • 返回值:成功返回0,失败返回非0

示例代码

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
int A = 0 ;
pthread_mutex_t mutex;
void* th(void* arg)
{
    // pthread_mutex_lock(&mutex);
    int i = 5000;
    while(i--)
    {
        pthread_mutex_lock(&mutex);
       int tmp = A;
        printf("A is %d\n",tmp+1);
        A = tmp+1;
        pthread_mutex_unlock(&mutex);
    }
    //pthread_mutex_unlock(&mutex);
    return NULL;
}

int	main(int argc, char **argv)
{
    
    pthread_t tid1,tid2;
    pthread_mutex_init(&mutex,NULL);
    pthread_create(&tid1,NULL,th,NULL);
    pthread_create(&tid2,NULL,th,NULL);

    pthread_join(tid1,NULL);
    pthread_join(tid2,NULL);
    pthread_mutex_destroy(&mutex);
    return 0;
}

2. 同步(Synchronization)

概念

按照一定先后顺序对资源的排他性访问。

与互斥的关系

  • 互斥包含同步,同步是互斥的一个特例

  • 互斥:只关心资源是否被占用

  • 同步:不仅关心资源,还关心访问顺序

3. 信号量(Semaphore)

与互斥锁的区别

  1. 加锁/解锁主体

    • 互斥锁:加锁和解锁必须是同一个线程

    • 信号量:可以由不同线程交叉释放(th1释放th2,th2释放th1)

  2. 使用场景

    • 互斥锁:临界区代码要短小精悍,不要有休眠或耗时操作

    • 信号量:可以有适当的休眠和小耗时操作

计数信号量

信号量初值可以大于1,用于多个资源的情况。

使用步骤

  1. 定义信号量

  2. 初始化信号量

  3. PV操作

  4. 销毁信号量

相关函数

1. 定义信号量
复制代码
sem_t sem;
2. 初始化信号量
复制代码
int sem_init(sem_t *sem, int pshared, unsigned int value);
  • 功能:初始化信号量

  • 参数

    • sem:要初始化的信号量

    • pshared

      • 0:线程间使用

      • 非0:进程间使用

    • value:信号量初始值

      • 二值信号量:0或1

      • 0:红灯,线程阻塞

      • 1:绿灯,线程可通过

  • 返回值:成功返回0,失败返回-1

3. PV操作
  • P操作 :申请资源 → sem_wait()

  • V操作 :释放资源 → sem_post()

sem_wait()

复制代码
int sem_wait(sem_t *sem);
  • 功能:申请信号量资源

  • 行为

    • 如果有资源(>0),申请资源,继续执行

    • 如果没资源(=0),线程阻塞等待

  • 注意 :自动执行 sem = sem - 1

  • 返回值:成功返回0,失败返回-1

sem_post()
复制代码
int sem_post(sem_t *sem);
  • 功能:释放信号量资源

  • 注意 :自动执行 sem = sem + 1

  • 返回值:成功返回0,失败返回-1

4. 销毁信号量
复制代码
int sem_destroy(sem_t *sem);

示例代码:

cpp 复制代码
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <semaphore.h>
sem_t sem_H,sem_W;

void* th1(void* arg)
{
    int i = 10;
    while(i--)
    {
        sem_wait(&sem_H);// sem_H -1  
        printf("hello ");
        fflush(stdout);
        sem_post(&sem_W);// sem_W +1

    }
    return NULL;
}

void* th2(void* arg)
{
    int i = 10;
    while(i--)
    {
        sem_wait(&sem_W);
        printf("world\n");
        sleep(1);
        sem_post(&sem_H);
    }
    return NULL;
}


int	main(int argc, char **argv)
{
    pthread_t tid1,tid2;
    sem_init(&sem_H,0,1);
    sem_init(&sem_W,0,0);
    pthread_create(&tid1, NULL, th1,NULL);
    pthread_create(&tid2, NULL, th2,NULL);
    pthread_join(tid1,NULL);
    pthread_join(tid2,NULL);

    sem_destroy(&sem_H);
    sem_destroy(&sem_W);

    return 0;
}

4. 死锁(Deadlock)

概念

由于锁资源安排不合理,导致进程/线程无法继续执行的现象。

产生死锁的四个必要条件

  1. 互斥条件:一个资源每次只能被一个进程使用

  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放

  3. 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺

  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系

死锁示例

复制代码
pthread_mutex_t mutexA = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutexB = PTHREAD_MUTEX_INITIALIZER;

// 线程1
void* thread1(void* arg) {
    pthread_mutex_lock(&mutexA);  // 获取锁A
    sleep(1);
    pthread_mutex_lock(&mutexB);  // 尝试获取锁B → 死锁!
    // ...
    pthread_mutex_unlock(&mutexB);
    pthread_mutex_unlock(&mutexA);
    return NULL;
}

// 线程2
void* thread2(void* arg) {
    pthread_mutex_lock(&mutexB);  // 获取锁B
    sleep(1);
    pthread_mutex_lock(&mutexA);  // 尝试获取锁A → 死锁!
    // ...
    pthread_mutex_unlock(&mutexA);
    pthread_mutex_unlock(&mutexB);
    return NULL;
}

5. 总结对比

特性 互斥锁(Mutex) 信号量(Semaphore)
用途 保护临界区,确保互斥访问 控制资源访问数量
资源数 通常保护单个资源 可以保护多个相同资源
加锁/解锁 必须由同一线程完成 可由不同线程完成
阻塞 锁被占用时线程阻塞 资源数为0时线程阻塞
计数 无计数功能 有计数功能
性能 轻量级,适用于短临界区 稍重,适用于较复杂同步

使用原则

  1. 能用互斥锁就用互斥锁,因为它更简单高效

  2. 临界区要短小,不要包含耗时操作

  3. 避免死锁:按固定顺序申请锁,或使用超时机制

  4. 锁的粒度要合适:不要过大(影响并发)或过小(增加开销)

相关推荐
yaoh.wang2 小时前
力扣(LeetCode) 66: 加一 - 解法思路
python·程序人生·算法·leetcode·面试·职场和发展·跳槽
爱吃泡芙的小白白2 小时前
Agent学习——xiaomi MiMo V2 Flash大模型的API申请
学习·agent·xiaomi mimo
牛奶咖啡132 小时前
Linux常见系统故障案例说明并修复解决(下)
linux·服务器·文件系统挂载异常分析并修复·持久化挂载分区文件丢失故障修复·分析系统进程cpu占用率过高
四谎真好看2 小时前
MySQL 学习笔记(进阶篇1)
笔记·学习·mysql·学习笔记
三品吉他手会点灯2 小时前
STM32F103学习笔记-19-SysTick-系统定时器(第1节)-功能框图讲解和优先级配置
笔记·stm32·单片机·嵌入式硬件·学习
java_logo2 小时前
Webtop Docker 容器化部署指南:基于浏览器的Linux桌面环境
linux·docker·容器·webtop·webtop部署教程·docker部署webtop·linux桌面
小易吾2 小时前
VISIO导出高清PDF有效方法
笔记·pdf
لا معنى له2 小时前
学习笔记:Transformer
人工智能·笔记·深度学习·学习·机器学习·transformer
wanderist.2 小时前
2025年蓝桥杯省赛C++大学A组
c++·算法·蓝桥杯