当前位置: 首页 > ai >正文

操作系统学习(七)——互斥

一、什么是互斥?

在操作系统和并发编程中,互斥(Mutual Exclusion) 是为了防止多个线程或进程同时访问共享资源(如内存、文件、变量等)所采取的一种机制。
它是实现并发安全(Concurrency Safety)的核心。

互斥的本质是:

同一时间只允许一个执行单元(线程或进程)访问某个临界资源(Critical Resource)。

例如:两个线程不能同时写同一个文件或同时修改一个变量,否则可能引发数据冲突或系统崩溃。

二、互斥的关键概念

概念描述
临界区(Critical Section)共享资源进行访问的代码区段
互斥锁(Mutex)控制对临界区访问的锁机制
锁(Lock)/解锁(Unlock)获得/释放资源访问权
同步协调多个线程执行顺序

三、互斥的实现

1. 软件实现(早期)

如经典的 Peterson算法Dekker算法 等,但效率不高,仅用于教学。

2. 硬件指令支持(低级原语)

利用原子指令(指那些在执行过程中不会被其他操作干扰要么完全执行,要么完全不执行的指令)实现:

  • Test-and-Set
  • Compare-and-Swap(CAS)
  • Load-Link / Store-Conditional

这些原语可以在硬件层面保证:多个线程对一个变量的读-改-写是原子的。

3. 操作系统提供的机制

类型平台说明
互斥锁(pthread_mutex_tLinux / POSIX最常用的线程锁机制
关键段(CRITICAL_SECTIONWindows类似互斥锁,但更轻量
std::mutexC++11标准封装操作系统互斥机制
synchronizedJava自动加锁关键字
threading.LockPythonPython GIL 下线程互斥

四、互斥的使用

互斥锁的使用流程:

[创建] ──▶ [上锁] ──▶ [临界区] ──▶ [解锁] ──▶ [销毁]↑        ↓仅允许一个线程进入

1. C语言 pthread 互斥锁:

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;void* task(void* arg) 
{pthread_mutex_lock(&mutex);   // 加锁// 访问临界区资源pthread_mutex_unlock(&mutex); // 解锁return NULL;
}

2. C++11 std::mutex

#include <mutex>
std::mutex mtx;void task() 
{mtx.lock(); // 加锁// 临界区mtx.unlock();  // 解锁
}

或使用更安全的 RAII 风格

std::lock_guard<std::mutex> lock(mtx);
// 自动加解锁

3. Python 中互斥锁:

import threading
lock = threading.Lock()def task():with lock:# 临界区代码pass

五、互斥存在的问题

问题描述
死锁(Deadlock)多线程互相等待对方释放锁,永远阻塞
饥饿(Starvation)某些线程始终抢不到锁,无法执行
优先级反转高优线程被低优线程持锁阻塞
性能下降锁竞争严重时会导致上下文频繁切换

六、死锁

死锁指两个或多个进程(线程)在执行过程中,因为争夺资源而造成一种互相等待的现象,若无外力干预,它们都将无法推进。

死锁示意:

线程A          线程B
持有锁1         持有锁2
请求锁2         请求锁1
→ 相互等待形成死锁

1. 死锁的条件(Prevention)

死锁的四个必要条件(Coffman 条件):
(1)互斥条件(Mutual Exclusion):至少有一个资源只能被一个进程使用。
(2)占有和等待条件(Hold and Wait):已持有资源的进程在等待获取其他资源时不释放已占资源。
(3)不可抢占条件(No Preemption):已分配资源不能被系统强制回收,只能由进程主动释放。
(4)环路等待条件(Circular Wait):存在一个进程资源环路:进程A等待B占用的资源,B又等待A占用的资源。

只要四个条件同时满足,就可能发生死锁。

2. 预防死锁

在程序设计时破坏死锁必要条件之一

  • 禁止请求与保持(加锁前先申请全部资源);
  • 设置资源获取顺序;
  • 允许资源抢占(如数据库)。

3. 避免死锁(Avoidance)

通过动态检查确保系统永远不进入死锁状态:
银行家算法:资源分配前检查是否仍处于“安全状态”

4. 死锁的检测与恢复(Detection + Recovery)

允许死锁发生,定期检测资源图中的“环”,发现死锁后终止或回滚某些进程。

5. 忽略死锁(鸵鸟策略)

部分系统(如 Linux)直接忽略死锁,只要概率极低或后果不严重。

七、读写锁

读写锁(Read-Write Lock,又称共享-互斥锁)是一种比互斥锁更高效的同步机制,适用于“读多写少”的并发场景
核心思想:

  • 多个线程可同时读共享资源(不互斥);
  • 写操作必须独占资源(与其他读写线程都互斥)。

1. POSIX C 语言:pthread_rwlock_t

#include <pthread.h>
#include <stdio.h>pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
int shared_data = 0;void* reader(void* arg) 
{pthread_rwlock_rdlock(&rwlock);      // 读锁printf("Reader: %d\n", shared_data);pthread_rwlock_unlock(&rwlock);      // 解锁return NULL;
}void* writer(void* arg) 
{pthread_rwlock_wrlock(&rwlock);      // 写锁shared_data += 1;printf("Writer updated to %d\n", shared_data);pthread_rwlock_unlock(&rwlock);      // 解锁return NULL;
}

API 对比表(POSIX)

操作函数
初始化pthread_rwlock_init()
读加锁pthread_rwlock_rdlock()
写加锁pthread_rwlock_wrlock()
尝试读锁pthread_rwlock_tryrdlock()
尝试写锁pthread_rwlock_trywrlock()
解锁pthread_rwlock_unlock()
销毁pthread_rwlock_destroy()

2. C++17 及以上(std::shared_mutex

#include <shared_mutex>
#include <thread>
#include <iostream>std::shared_mutex rwlock;
int shared_data = 0;void reader() 
{std::shared_lock<std::shared_mutex> lock(rwlock);  // 读锁std::cout << "Reader: " << shared_data << "\n";
}void writer() 
{std::unique_lock<std::shared_mutex> lock(rwlock);  // 写锁shared_data += 1;std::cout << "Writer updated to " << shared_data << "\n";
}

3. Python(标准库无原生读写锁,需要自实现或第三方库)

简易实现:

import threadingclass ReadWriteLock:def __init__(self):self.lock = threading.Lock()self.readers = 0self.read_lock = threading.Lock()def acquire_read(self):with self.read_lock:self.readers += 1if self.readers == 1:self.lock.acquire()def release_read(self):with self.read_lock:self.readers -= 1if self.readers == 0:self.lock.release()def acquire_write(self):self.lock.acquire()def release_write(self):self.lock.release()

八、适当使用互斥的原则

  1. 锁粒度适中:不要锁太大范围(性能低),也不能锁太小(频繁加锁开销高);
  2. 尽量减少锁嵌套:避免死锁;
  3. 读多写少用读写锁(rwlock
  4. 避免忙等待:使用条件变量代替轮询;
  5. 优先使用语言内建机制(如 C++ std::lock_guard,Python with)。
http://www.xdnf.cn/news/10295.html

相关文章:

  • 深入Java性能调优:原理详解与实战
  • STM32F103C8T6,bxCAN收发配置实例,包含ID过滤
  • 香港中乐团六月京津巡演 携多位国际艺术家献演
  • 边缘计算场景下的大模型落地:基于 Cherry Studio 的 DeepSeek-R1-0528 本地部署
  • spring事务的面试题 —— 事务的特性、传播机制、隔离机制、注解
  • 趋势直线指标
  • 机器视觉2D定位引导-合同要点重度讲解-技术要点及注意事项
  • Web开发实战:HTML+CSS+JS期末复习全梳理
  • 动态规划-376.摆动序列-力扣(LeetCode)
  • C++学习打卡
  • AI书签管理工具开发全记录(八):Ai创建书签功能实现
  • MSMQ消息队列》》Rabbit MQ》》安装延迟插件、延迟消息
  • 3D-激光SLAM笔记
  • Rollup打包输出产物遇到的一个坑。(分享心得)
  • Redis缓存问题重点详解
  • 57、IdentityServer4概述
  • [创业之路-398]:企业战略管理案例分析-战略意图是使命、愿景可聚焦、可量化、可落地、可实现、具象化的3-5年左右的目标
  • 三步问题 --- 动态规划
  • 二叉搜索树——AVL
  • 小红书 发评论 分析 x-s x-t
  • 在win10/11下Node.js安装配置教程
  • 网络编程1_网络编程引入
  • Centos环境下安装/重装MySQL完整教程
  • [SC]SystemC在CPU/GPU验证中的应用(二)
  • 【数据结构】图的存储(邻接矩阵与邻接表)
  • Spring Data Redis 实战指南
  • Java对象克隆:从浅到深的奥秘
  • 秒杀系统—5.第二版升级优化的技术文档三
  • Brighter 的线程模型:为何专用线程驱动异步消息泵
  • Python(十四)