新闻详情

新闻详情

首页 / 资讯中心 / 详情

CS-Base 图解操作系统:多线程冲突与互斥同步全解(自旋锁、信号量、PV 操作与经典同步问题)

发布时间:2026/10/2 8:12:13来源:尧图网络
CS-Base 图解操作系统:多线程冲突与互斥同步全解(自旋锁、信号量、PV 操作与经典同步问题)
文档教程知识库【免费下载链接】CS-Base图解计算机网络、操作系统、计算机组成、数据库共 1000 张图 50 万字破除晦涩难懂的计算机基础知识让天下没有难懂的八股文 在线阅读https://xiaolincoding.com项目地址https://gitcode.com/GitHub_Trending/cs/CS-Base点击查看免费下载本文基于 CS-Base 仓库 多线程冲突了怎么办 一文展开系统讲解多线程访问共享资源时产生的竞争条件race condition以及操作系统提供的互斥mutual exclusion与同步synchronization两大协作机制。你将掌握临界区、忙等待锁自旋锁与信号量PV 操作的原理与实现并完整理解生产者-消费者、哲学家就餐、读者-写者三大经典同步问题的多种解法与取舍为阅读 怎么避免死锁 和 什么是悲观锁、乐观锁 等后续章节打下坚实基础。竞争与协作多线程为什么会在共享资源上翻车从并发到多线程共享资源出现的必然性在单核 CPU 系统里为了实现多个程序同时运行的假象操作系统通常以时间片调度的方式让每个进程每次执行一个时间片时间片用完后切换下一个进程运行。由于时间片极短于是便造成了「并发」的现象。与此同时操作系统还为每个进程创造了巨大、私有的虚拟内存假象这种地址空间的抽象让每个程序仿佛拥有自己的内存而实际上操作系统在背后让多个地址空间「复用」物理内存或磁盘。如果一个程序只有一个执行流程它是单线程的而一个程序可以有多个执行流程即多线程程序。在操作系统的分工中线程是调度的基本单位进程则是资源分配的基本单位。线程之间可以共享进程的资源比如代码段、堆空间、数据段、打开的文件等但每个线程都拥有自己独立的栈空间。这正是多线程与多进程的根本差异也是 进程、线程基础知识 一章的核心结论。用 3 条汇编指令复现一次数据竞争多个线程如果竞争共享资源而不采取有效措施就会造成共享数据的混乱。我们做一个小实验创建两个线程分别对共享变量i自增 1 执行 10000 次。按理i的最终值应为20000但实际运行时两次结果却可能是15173也可能是20000——每次运行不但可能出错而且结果不确定。原因藏在汇编层面只是给i加上数字 1在 CPU 运行时要执行 3 条指令——从内存加载i到寄存器、寄存器加 1、寄存器写回内存。设想线程 1 进入这段代码区域将i的值假设为 50从内存加载到寄存器然后加 1此时寄存器中i为 51。这时不幸的事情发生了时钟中断。操作系统将当前线程状态保存到线程控制块 TCB。随后线程 2 被调度并进入同一段代码它也从内存获取i此时内存中仍是 50执行加 1 并写回全局变量i变成 51。最后上下文切换回线程 1它寄存器中的值仍是 51执行最后一条指令写回内存全局变量i又被设置为 51。简单来说i值为 50的自增代码被执行了两次按理结果应为 52但由于不可控的调度最终却是 51。这种因多线程竞争共享变量、在执行过程中发生上下文切换而得到错误结果的情况称为竞争条件race condition其输出结果存在不确定性indeterminate。临界区与互斥、同步的定义由于多线程执行操作共享变量的这段代码可能导致竞争状态因此这段代码被称为临界区critical section——它是访问共享资源的代码片段一定不能让多线程同时执行。我们希望这段代码是**互斥mutual exclusion**的即保证一个线程在临界区执行时其他线程应被阻止进入临界区也就是这段代码执行过程中最多只能出现一个线程。需要强调的是互斥并不只针对多线程多进程竞争共享资源时同样可以用互斥来避免资源混乱。互斥解决了并发进程/线程对临界区的使用问题但线程并不总是顺序执行的它们以各自独立、不可预知的速度推进有时我们又希望多个线程密切合作完成共同任务。比如线程 1 负责读入数据线程 2 负责处理数据线程 2 在没有收到线程 1 的唤醒通知前一直阻塞等待线程 1 读完数据后唤醒线程 2 并把数据交给它处理。所谓同步就是并发进程/线程在一些关键点上需要互相等待与互通消息这种相互制约的等待与互通信息称为进程/线程同步。注意同步与互斥是两种不同概念同步「操作 A 应在操作 B 之前执行」「操作 C 必须在操作 A 和操作 B 都完成之后才能执行」互斥「操作 A 和操作 B 不能在同一时刻执行」。互斥与同步的实现锁与信号量为了实现进程/线程间正确的协作操作系统提供了两种主要方法锁加锁、解锁操作信号量P、V 操作。两者都能方便地实现互斥而信号量功能更强还能方便地实现同步。锁加锁与解锁任何想进入临界区的线程必须先执行加锁操作加锁通过则可进入临界区完成访问后再解锁释放临界资源。根据锁的实现不同可分为「忙等待锁」和「无忙等待锁」。忙等待锁自旋锁实现忙等待锁之前先介绍现代 CPU 提供的特殊原子操作指令——测试和置位Test-and-Set指令。用 C 语言表示如下int TestAndSet(int *old_ptr, int new) { int old *old_ptr; // 取旧值 *old_ptr new; // 设置新值 return old; // 返回旧值 }该指令做了两件事把old_ptr更新为new的新值并返回old_ptr的旧值。关键是这些代码是原子执行的——既能测试旧值又能设置新值故得名「测试并设置」。所谓原子操作就是要么全部执行要么都不执行不能出现执行到一半的中间状态。基于 Test-and-Set 实现忙等待锁void lock(int *flag) { while (TestAndSet(flag, 1) 1) // 1 表示锁已被持有忙等 ; // 空转 } void unlock(int *flag) { *flag 0; }理解这个锁为何能工作需要看两个场景场景一一个线程调用lock()此时没有其他线程持有锁flag为 0。调用TestAndSet(flag, 1)返回 0线程跳出 while 循环获取锁同时原子地把flag设为 1 标志锁已被持有线程离开临界区调用unlock()将flag清理为 0。场景二某线程已持有锁flag为 1。本线程调用lock()TestAndSet(flag, 1)返回 1只要另一个线程一直持有锁TestAndSet()会重复返回 1本线程一直忙等当flag终于被改为 0TestAndSet()返回 0 并原子置 1从而获得锁进入临界区。当获取不到锁时线程就一直 while 循环、不做任何事因此这种锁被称为「忙等待锁」也就是自旋锁spin lock。自旋锁持续占用 CPU 周期直到锁可用在单处理器上必须配合抢占式调度器不断通过时钟中断切换线程否则自旋线程永远不会放弃 CPU。补充从项目 什么是悲观锁、乐观锁 一文可知现代实现中自旋锁的加锁通常通过 CPU 提供的CASCompare And Swap函数在用户态完成将「查看锁状态」和「设置为持有」两步合并为一条硬件级原子指令忙等待最好使用 CPU 的PAUSE指令实现以降低耗电量。自旋锁开销小、不主动产生线程切换适合被锁代码执行时间很短的场景但被锁代码过长时自旋线程会长时间占用 CPU。无忙等待锁无等待锁顾名思义获取不到锁时不自旋而是把当前线程放入锁的等待队列然后执行调度程序把 CPU 让给其他线程。等锁被释放后再由调度程序唤醒队列中的等待线程。虽然具体操作系统的实现更复杂但都离不开上述两种锁的基本元素。信号量P 操作与 V 操作信号量是操作系统提供的一种协调共享资源访问的方法。通常信号量表示资源的数量对应一个整型sem变量。此外还有两个原子操作的系统调用函数控制信号量P 操作将sem减 1相减后若sem 0则进程/线程进入阻塞等待否则继续——P 操作可能会阻塞V 操作将sem加 1相加后若sem 0唤醒一个等待中的进程/线程——V 操作不会阻塞。P 操作用在进入临界区之前V 操作用在离开临界区之后二者必须成对出现。类比理解2 个资源的信号量相当于 2 条火车轨道P 操作占用一条轨道V 操作释放一条轨道。信号量的数据结构与 PV 操作由操作系统管理和实现因此执行 PV 函数时天然具备原子性。用信号量实现互斥互斥信号量为每类共享资源设置一个信号量s初值为1表示临界资源未被占用。只要把进入临界区的操作置于P(s)和V(s)之间即可实现进程/线程互斥P(s); // 进入临界区前 // 访问临界资源 V(s); // 离开临界区后此时任何想进入临界区的线程必先在互斥信号量上执行 P 操作完成访问后再执行 V 操作。由于互斥信号量初值为 1第一个线程执行 P 后s变为 0表示临界资源已分配给该线程若第二个线程执行 Ps变为负值说明临界资源被占用第二个线程被阻塞直到第一个线程执行 V 使s恢复为 0才唤醒第二个线程待其完成访问后再次执行 V使s恢复到初始值 1。对于两个并发线程互斥信号量的值仅取 1、0、-1 三个值1没有线程进入临界区0有一个线程进入临界区-1一个线程进入临界区另一个线程等待进入。用信号量实现同步事件同步同步的方式是设置一个初值为0的信号量。我们把「吃饭 - 做饭」的同步例子用代码实现// 妈妈线程 P(s1); // 询问儿子要不要做饭s1 初值 0P 后 s1 -1妈妈阻塞等待 // 做饭... V(s2); // 做完饭唤醒等待的儿子s2 从 -1 变为 0 // 儿子线程 V(s1); // 肚子饿了唤醒妈妈s1 从 -1 变为 0 // 等待... P(s2); // 询问妈妈饭做完了吗s2 初值 0P 后 s2 -1儿子阻塞等待 // 吃饭...流程推演妈妈一开始执行P(s1)相当于询问儿子需不需要吃饭因s1初值为 0P 后变为 -1表明儿子不需要吃饭妈妈线程进入等待儿子肚子饿时执行V(s1)s1从 -1 变为 0唤醒阻塞中的妈妈开始做饭接着儿子执行P(s2)询问饭是否做完因s2初值为 0P 后变为 -1儿子进入等待妈妈做完饭后执行V(s2)s2从 -1 变回 0唤醒儿子线程开始吃饭。生产者 - 消费者问题生产者-消费者问题描述如下生产者在生成数据后放在一个缓冲区中消费者从缓冲区取出数据处理任何时刻只能有一个生产者或消费者访问缓冲区。问题分析可得出两条结论任何时刻只能有一个线程操作缓冲区说明操作缓冲区是临界代码需要互斥缓冲区空时消费者必须等待生产者生成数据、缓冲区满时生产者必须等待消费者取出数据说明生产者和消费者需要同步。因此需要三个信号量信号量作用初值mutex互斥访问缓冲区1fullBuffers消费者询问缓冲区是否有数据0缓冲区一开始为空emptyBuffers生产者询问缓冲区是否有空位n缓冲区大小具体实现代码semaphore mutex 1; // 临界区互斥信号量 semaphore fullBuffers 0; // 缓冲区已用槽位 semaphore emptyBuffers n; // 缓冲区剩余空槽 // 生产者 while (true) { P(emptyBuffers); // 申请一个空槽没有空槽则阻塞 P(mutex); // 进入临界区 // 将数据放入缓冲区 V(mutex); // 离开临界区 V(fullBuffers); // 已用槽位 1唤醒等待的消费者 } // 消费者 while (true) { P(fullBuffers); // 申请一个数据槽没有数据则阻塞 P(mutex); // 进入临界区 // 从缓冲区取出数据 V(mutex); // 离开临界区 V(emptyBuffers); // 空槽 1唤醒等待的生产者 }执行推演如果消费者一开始执行P(fullBuffers)因fullBuffers初值为 0其值变为 -1说明缓冲区没有数据消费者只能等待接着生产者执行P(emptyBuffers)减少一个空槽若没有其他生产者在临界区便可把数据放入缓冲区放完执行V(fullBuffers)fullBuffers从 -1 变为 0唤醒阻塞等待的消费者消费者被唤醒后若没有其他消费者在读数据即可进入临界区读取离开后把空槽个数加 1。注意区分这里mutex与资源信号量的分工mutex只保证缓冲区操作的互斥fullBuffers与emptyBuffers则负责生产与消费之间的同步协调。这套「互斥信号量 资源信号量」的组合也是后续 死锁 讨论中破坏「持有并等待」等条件的基础语境。经典同步问题一哲学家就餐问题问题描述5 个哲学家围绕一张圆桌吃面桌上只有 5 支叉子每两个哲学家之间放一支叉子哲学家先思考饿了就想进餐但要拿到左右两边的两支叉子才愿意吃面吃完后把叉子放回原处继续思考。问题在于如何保证动作有序进行而不会出现有人永远拿不到叉子。方案一直接 PV 操作会死锁#define N 5 // 哲学家数量 semaphore fork[5]; // 每支叉子一个信号量初值为 1 void philosopher(int i) { while (true) { P(fork[i]); // 拿左边的叉子 P(fork[(i 1) % N]); // 拿右边的叉子 // 吃面... V(fork[i]); // 放回左边的叉子 V(fork[(i 1) % N]); // 放回右边的叉子 } }这个解法看似自然却存在极端问题假设五位哲学家同时拿起左边的叉子桌面上就没有叉子了每位哲学家都会阻塞在P(fork[(i 1) % N])上明显发生了死锁。这与 怎么避免死锁 中死锁四条件互斥、持有并等待、不可剥夺、环路等待完全吻合——每个人都持有一支叉子并等待下一支形成环路等待。方案二加互斥信号量不会死锁但低效既然方案一会因同时竞争左边叉子而死锁就在拿叉子前加一个互斥信号量semaphore mutex 1; void philosopher(int i) { while (true) { P(mutex); // 进入临界区 P(fork[i]); // 拿左边的叉子 P(fork[(i 1) % N]); // 拿右边的叉子 V(mutex); // 离开临界区 // 吃面... V(fork[i]); V(fork[(i 1) % N]); } }互斥信号量的作用在于只要有一个哲学家进入了临界区准备拿叉子其他哲学家都不能动只有这位哲学家用完叉子才能轮到下一位进餐。方案二虽然能让哲学家按顺序吃饭、避免死锁但每次进餐只能有一位哲学家而桌上有 5 把叉子按道理可允许两位哲学家同时进餐因此从效率上看不是最优解。方案三按编号改变拿叉顺序方案二用互斥信号量导致只能一人就餐那就不用它方案一的症结在于所有哲学家可能同时拿左边叉子那就避免这种情况——采用分支结构根据哲学家编号采取不同动作偶数编号「先拿左边的叉子后拿右边的叉子」奇数编号「先拿右边的叉子后拿左边的叉子」void philosopher(int i) { while (true) { if (i % 2 0) { // 偶数哲学家 P(fork[i]); // 先拿左边 P(fork[(i 1) % N]); // 再拿右边 } else { // 奇数哲学家 P(fork[(i 1) % N]); // 先拿右边 P(fork[i]); // 再拿左边 } // 吃面... V(fork[i]); V(fork[(i 1) % N]); } }V 操作不需要分支因为 V 操作不会阻塞。方案三既不会出现死锁也可以让两位哲学家同时进餐相邻的两个哲学家拿叉顺序相反不会互相抢同一支。方案四状态数组 信号量数组另一种可行方案是用一个数组state记录每一位哲学家的三个状态思考状态、饥饿状态正在试图拿叉子、进餐状态。一个哲学家只有在两个邻居都没有进餐时才可以进入进餐状态。第i位哲学家的左右邻居由宏LEFT和RIGHT定义LEFT(i 5 - 1) % 5RIGHT(i 1) % 5例如i为 2则LEFT为 1RIGHT为 3。#define N 5 #define LEFT (i N - 1) % N #define RIGHT (i 1) % N #define THINKING 0 #define HUNGRY 1 #define EATING 2 int state[N]; // 记录每位哲学家的状态 semaphore mutex 1; // 互斥地修改 state semaphore s[N]; // 每个哲学家一个信号量初值为 0 void test(int i) { if (state[i] HUNGRY state[LEFT] ! EATING state[RIGHT] ! EATING) { state[i] EATING; V(s[i]); // 唤醒第 i 位哲学家 } } void take_forks(int i) { P(mutex); state[i] HUNGRY; test(i); // 尝试获取叉子 V(mutex); P(s[i]); // 拿不到叉子则阻塞 } void put_forks(int i) { P(mutex); state[i] THINKING; test(LEFT); // 看左邻居能否进餐 test(RIGHT); // 看右邻居能否进餐 V(mutex); } void philosopher(int i) { // 主代码每位哲学家一个线程 while (true) { // 思考... take_forks(i); // 吃面... put_forks(i); } }该程序使用信号量数组每个信号量对应一位哲学家在所需叉子被占用时想进餐的哲学家被阻塞。注意每个进程/线程将philosopher函数作为主代码运行而take_forks、put_forks、test只是普通函数并非单独的进程/线程。方案四同样不会出现死锁也可两人同时进餐。经典同步问题二读者 - 写者问题哲学家就餐问题对互斥访问有限竞争问题如 I/O 设备的建模十分有用另一个著名问题是「读者 - 写者」它为数据库访问建立了模型。读者只读取数据、不修改数据写者既可以读也可以修改数据。读者-写者问题的规则「读 - 读」允许同一时刻允许多个读者同时读「读 - 写」互斥没有写者时读者才能读没有读者时写者才能写「写 - 写」互斥没有其他写者时写者才能写。方案一读者优先使用信号量的方式解决信号量wMutex控制写操作的互斥信号量初值为 1读者计数rCount正在进行读操作的读者个数初值为 0信号量rCountMutex控制对rCount的互斥修改初值为 1。semaphore wMutex 1; // 写操作互斥 semaphore rCountMutex 1; // 保护读者计数 int rCount 0; // 当前读者数量 // 读者 void reader() { while (true) { P(rCountMutex); // 保护 rCount if (rCount 0) P(wMutex); // 第一个读者锁住写者 rCount; V(rCountMutex); // 读取数据... P(rCountMutex); rCount--; if (rCount 0) V(wMutex); // 最后一个读者释放写者 V(rCountMutex); } } // 写者 void writer() { while (true) { P(wMutex); // 申请写锁 // 写入数据... V(wMutex); // 释放写锁 } }这是读者优先策略只要有读者正在读后来的读者都可以直接进入如果读者持续不断进入写者就会处于饥饿状态。方案二写者优先在方案一基础上新增如下变量信号量rMutex控制读者进入的互斥信号量初值为 1信号量wDataMutex控制写者写操作的互斥信号量初值为 1写者计数wCount记录写者数量初值为 0信号量wCountMutex控制wCount的互斥修改初值为 1。semaphore rMutex 1; // 控制读者进入 semaphore wDataMutex 1; // 控制写操作 semaphore wCountMutex 1; // 保护写者计数 int wCount 0; // 当前写者数量 // 读者 void reader() { while (true) { P(rMutex); // 被写者阻塞时读者无法进入 P(rCountMutex); if (rCount 0) P(wDataMutex); // 第一个读者锁住写者 rCount; V(rCountMutex); V(rMutex); // 读取数据... P(rCountMutex); rCount--; if (rCount 0) V(wDataMutex); V(rCountMutex); } } // 写者 void writer() { while (true) { P(wCountMutex); if (wCount 0) P(rMutex); // 第一个写者阻止新读者进入 wCount; V(wCountMutex); P(wDataMutex); // 申请写锁 // 写入数据... V(wDataMutex); P(wCountMutex); wCount--; if (wCount 0) V(rMutex); // 最后一个写者放行读者 V(wCountMutex); } }这里rMutex的作用开始有多个读者读数据、全部进入读者队列此时来了一个写者执行P(rMutex)之后后续读者都阻塞在rMutex上不能再进入读者队列而写者到来则可以全部进入写者队列因此保证了写者优先。同时第一个写者执行P(rMutex)后也不能马上开始写必须等到所有已进入读者队列的读者都执行完读操作通过V(wDataMutex)唤醒写者的写操作。写者优先策略的问题在于如果有写者持续不断写入读者就会饥饿。方案三公平策略读者优先和写者优先都会造成饥饿于是实现公平策略其要求为优先级相同写者、读者互斥访问只能一个写者访问临界区可以有多个读者同时访问临界资源。semaphore flag 1; // 公平竞争的关键信号量 semaphore wDataMutex 1; // 控制写操作 semaphore rCountMutex 1; // 保护读者计数 int rCount 0; // 读者 void reader() { while (true) { P(flag); // 与写者公平竞争 P(rCountMutex); if (rCount 0) P(wDataMutex); rCount; V(rCountMutex); V(flag); // 读取数据... P(rCountMutex); rCount--; if (rCount 0) V(wDataMutex); V(rCountMutex); } } // 写者 void writer() { while (true) { P(flag); // 与读者公平竞争 P(wDataMutex); // 写入数据... V(wDataMutex); V(flag); } }为什么加了一个信号量flag就实现了公平竞争对比方案一的读者优先策略可以发现读者优先中只要后续有读者到达读者就可以进入读者队列而写者必须等到读者队列为空即rCount 0才能进入临界区。而flag的作用就是阻止读者的这种「只要到达就能进入读者队列」的特殊权限。推演开始来了一些读者读数据它们全部进入读者队列此时来了一个写者执行P(flag)后续到来的读者都阻塞在flag上不能进入读者队列使得读者队列逐渐变空rCount减为 0这个写者也不能立刻开始写因为读者队列还不为空会阻塞在wDataMutex上当读者队列中的读者全部读取结束最后一个读者进程执行V(wDataMutex)唤醒写者写者继续执行写操作。由于读者与写者都要先竞争flag双方机会对等既不会出现写者饥饿也不会出现读者饥饿。从互斥同步到死锁与锁的选型仓库知识脉络串联多线程同步与互斥是整个进程管理章节的地基CS-Base 仓库在 os/4_process 目录下围绕这一主题构建了完整的知识链进程、线程基础知识线程共享进程资源代码段、堆、数据段、打开文件、拥有独立栈空间是理解共享资源冲突的前提怎么避免死锁死锁只有在互斥、持有并等待、不可剥夺、环路等待四个条件同时满足时才发生哲学家就餐方案一的死锁正是四条件的典型实例避免死锁最常见的方法是资源有序分配法——让所有线程以相同顺序申请资源破坏环路等待条件。该文还演示了 Linux 下用pstackgdb以及 Java 下的jstack排查死锁的完整流程什么是悲观锁、乐观锁互斥锁加锁失败会释放 CPU、产生两次线程上下文切换成本自旋锁加锁失败则忙等待若明确被锁代码执行时间很短应选自旋锁能区分读写场景时选读写锁冲突概率极低且加锁成本高时才考虑乐观锁。这三篇文档与本文共同构成一份从「竞争发生 → 互斥同步手段 → 死锁规避 → 锁选型」的完整图谱读者可按此顺序系统阅读。总结多线程访问共享资源时不加锁就可能在执行过程中因上下文切换发生竞争条件产生不确定的错误结果。解决之道是互斥与同步互斥保证临界区任意时刻最多一个线程执行可通过 Test-and-Set 原子指令实现自旋锁或用初值为 1 的互斥信号量实现同步保证线程间在关键点上互相等待与互通消息可用初值为 0 的信号量实现。生产者-消费者问题展示了「互斥信号量 资源信号量」的经典组合哲学家就餐问题通过四种方案朴素 PV 的死锁、互斥信号量的低效、按编号换序的高效、状态数组方案揭示了资源竞争建模的要点读者-写者问题则完整呈现了读者优先、写者优先、公平三种策略的饥饿问题与取舍。理解这些经典模型之后推荐继续阅读 怎么避免死锁 掌握死锁的排查工具pstack/gdb/jstack与资源有序分配法并结合 什么是悲观锁、乐观锁 在工程实践中根据锁的粒度与冲突概率做出正确的锁选型。赞分享文档教程知识库【免费下载链接】CS-Base图解计算机网络、操作系统、计算机组成、数据库共 1000 张图 50 万字破除晦涩难懂的计算机基础知识让天下没有难懂的八股文 在线阅读https://xiaolincoding.com项目地址https://gitcode.com/GitHub_Trending/cs/CS-Base点击查看免费下载相关推荐OpenHarmony多线程同步互斥锁与信号量使用技巧OpenHarmony多线程同步互斥锁与信号量使用技巧 引言并发编程的痛点与解决方案 你是否曾在OpenHarmony应用开发中遇到过这些问题多线程访问共文档教程OpenHarmonyArea51多线程同步原语互斥锁、信号量与条件变量Area51多线程同步原语互斥锁、信号量与条件变量 在多线程编程中同步原语是确保线程安全的核心机制。Area51项目通过精心设计的互斥锁Mutex、信号Linux 内核同步原语全景自旋锁、信号量、互斥锁、读写信号量与顺序锁的实现剖析Linux 内核同步原语全景自旋锁、信号量、互斥锁、读写信号量与顺序锁的实现剖析 本文是 linux insides zhLinux 内核揭秘仓库 Syn上一篇APA第7版参考文献格式终极指南3分钟快速上手免费专业工具下一篇5个让排查效率翻倍的技巧Logger配合Logcat过滤与显示设置实战清单创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

华为IPD+CMMI+Scrum在国产RDM系统中的工程化落地 2026/10/2 8:55:42

华为IPD+CMMI+Scrum在国产RDM系统中的工程化落地

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
基于PyTorch的一维CNN滚动轴承故障诊断实战指南 2026/10/2 8:55:42

基于PyTorch的一维CNN滚动轴承故障诊断实战指南

简介:工业设备的可靠性维护是智能制造的核心环节,而滚动轴承作为旋转机械中最易损的部件,其故障诊断长期依赖专家经验与人工特征提取。随着深度学习技术的普及,振动信号分析正从传统阈值判断转向端到端的智能分类——将时域波形直…

阅读更多 →
openclaw部署实战:从零接入飞书笔记自动化流程 2026/10/2 8:55:42

openclaw部署实战:从零接入飞书笔记自动化流程

最近一直在用openclaw搭个人知识库的自动化流程,前前后后折腾了两周,从零开始部署到最终接通飞书笔记,踩了不少坑,也总结出一些还算可靠的经验。这篇笔记就完整记录一下openclaw部署的全过程,以及如何把它和飞书开放平…

阅读更多 →
OpenClaw部署实战:从WSL2环境到Qwen2.5本地模型接入 2026/10/2 8:55:42

OpenClaw部署实战:从WSL2环境到Qwen2.5本地模型接入

最近我把 OpenClaw 完整部署了一遍。从 Windows 侧准备 WSL2 环境,到 Ubuntu 里装 Node.js、拉源码、装依赖,再到接入 Qwen2.5 本地模型和 Microsoft Teams 渠道,整个过程踩了不少坑。尤其是“无法安全验证 WSL2 环境”这个报错,我…

阅读更多 →
差分数组入门:从“最高的牛”理解区间修改的O(1)技巧 2026/10/2 8:55:41

差分数组入门:从“最高的牛”理解区间修改的O(1)技巧

先想清楚一个问题:这道题为什么叫“最高的牛(差分”?我刚接触的时候也愣了一下,差分我知道,是前缀和的逆运算,一个处理区间修改的常用技巧。但“最高的牛”是什么鬼?刷了几道题才明白&#xff0…

阅读更多 →
OpenShell 终端增强:从分屏工作区到会话恢复的高效配置指南 2026/10/2 8:55:35

OpenShell 终端增强:从分屏工作区到会话恢复的高效配置指南

每天打开终端几十次的人,一定懂那种窗口乱飞的烦躁感:左边一个日志窗口、右边一个调试窗口、上面还挂着编译任务,稍微一忙就分不清哪个是哪个。我试过很多终端工具,从系统自带的到各种第三方增强方案,各有各的优势&…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉