【Linux】线程同步与互斥深度解析:从锁机制到生产者消费者模型

【Linux】线程同步与互斥深度解析:从锁机制到生产者消费者模型

目录

一、引言:多线程共享资源的问题
    1.1 为什么需要同步与互斥?
    1.2 核心概念铺垫
二、线程互斥:用互斥量(mutex)守护临界资源
    2.1 互斥的核心:临界资源与临界区
    2.2 互斥量接口与实战
    2.3 RAII风格锁封装:避免锁泄漏
三、线程同步:条件变量(cond)实现有序协作
    3.1 同步的意义:解决“竞态条件”
    3.2 条件变量接口与核心原理
    3.3 条件变量使用规范:避免伪唤醒
    3.4 条件变量封装:与互斥量解耦
四、生产者消费者模型:同步互斥的经典实战
    4.1 模型核心:321原则
    4.2 实现一:基于阻塞队列(动态大小)
    4.3 实现二:基于环形队列(固定大小)
    4.4 两种模型对比与选型
五、线程安全与可重入问题
    5.1 线程安全:多线程访问的一致性保障
    5.2 可重入函数:重入场景下的正确性
    5.3 核心区别与联系
六、死锁
    6.1 死锁的四个必要条件
    6.2 避免死锁的实战方法
七、总结


一、引言:多线程共享资源的问题

多线程的核心优势是共享进程资源、提高并发效率,但共享资源带来了一个致命问题——竞态条件(Race Condition):多个线程并发操作共享资源时,由于执行时序不确定,导致最终结果与预期不符。比如多线程售票系统可能卖出负数票,银行转账可能导致余额不一致。
线程同步与互斥的核心目标,就是在保证多线程并发的同时,解决竞态条件,确保数据一致性。

1.1 为什么需要同步与互斥?

举个直观例子:未加保护的售票系统

#include<stdio.h>#include<pthread.h>#include<unistd.h>int ticket =100;// 共享资源void*sell_ticket(void* arg){char* id =(char*)arg;while(1){if(ticket >0){usleep(1000);// 模拟业务耗时printf("%s sells ticket:%d\n", id, ticket); ticket--;// 非原子操作}else{break;}}}intmain(){ pthread_t t1, t2, t3, t4;pthread_create(&t1,NULL, sell_ticket,(void*)"thread 1");pthread_create(&t2,NULL, sell_ticket,(void*)"thread 2");pthread_create(&t3,NULL, sell_ticket,(void*)"thread 3");pthread_create(&t4,NULL, sell_ticket,(void*)"thread 4");pthread_join(t1,NULL);pthread_join(t2,NULL);pthread_join(t3,NULL);pthread_join(t4,NULL);return0;}

运行结果:出现负数票

在这里插入图片描述


原因

  1. if (ticket > 0)判断后,线程可能被切换,其他线程继续修改ticket
  2. ticket--对应三条汇编指令(load→update→store),非原子操作,可能被打断。

解决思路:互斥保证同一时间只有一个线程访问临界资源,同步保证线程按合理顺序访问。

1.2 核心概念铺垫

  • 共享资源:多个线程均可访问的资源(如全局变量、堆内存、文件描述符);
  • 临界资源:需要被保护的共享资源(如售票系统的ticket);
  • 临界区:访问临界资源的代码段(如if (ticket > 0)ticket--的代码);
  • 互斥:任何时刻仅允许一个线程进入临界区,保证资源独占访问;
  • 同步:在互斥基础上,保证线程按预期顺序访问资源(如生产者先生产,消费者后消费);
  • 原子操作:不可被打断的操作(如硬件提供的swap指令),是互斥的底层基础。

二、线程互斥:用互斥量(mutex)守护临界资源

互斥量(mutex)是Linux提供的核心互斥工具,本质是一把“锁”,通过原子操作实现锁的获取与释放,确保临界区的独占访问。

2.1 互斥的核心:临界资源与临界区

  • 临界区必须是“最小粒度”:仅包含访问临界资源的必要代码,避免过度阻塞影响并发效率;
  • 互斥的三大要求:
    1. 互斥性:同一时间仅一个线程进入临界区;
    2. 空闲让进:临界区空闲时,允许等待线程进入;
    3. 有限等待:线程等待锁的时间有限,避免饥饿。

2.2 互斥量接口与实战

POSIX线程库提供互斥量相关接口,需包含<pthread.h>,链接时加-lpthread

2.2.1 互斥量核心接口
接口功能关键说明
pthread_mutex_init动态初始化互斥量attr=NULL使用默认属性
PTHREAD_MUTEX_INITIALIZER静态初始化互斥量全局或静态互斥量使用,无需销毁
pthread_mutex_lock加锁阻塞等待,直到获取锁
pthread_mutex_unlock解锁必须由加锁线程解锁
pthread_mutex_destroy销毁互斥量静态初始化的互斥量无需销毁
2.2.2 实战:改进售票系统
#include<stdio.h>#include<pthread.h>#include<unistd.h>int ticket =100; pthread_mutex_t mutex;void*sell_ticket(void* arg){char* id =(char*)arg;while(1){int current_ticket =0;// 临界区:只保护共享数据的读写pthread_mutex_lock(&mutex);if(ticket <=0){pthread_mutex_unlock(&mutex);break;} current_ticket = ticket; ticket--;pthread_mutex_unlock(&mutex);// 模拟耗时操作usleep(1000);printf("%s sells ticket:%d\n", id, current_ticket);}returnNULL;}intmain(){ pthread_t t1, t2, t3, t4;pthread_mutex_init(&mutex,NULL);pthread_create(&t1,NULL, sell_ticket,(void*)"thread 1");pthread_create(&t2,NULL, sell_ticket,(void*)"thread 2");pthread_create(&t3,NULL, sell_ticket,(void*)"thread 3");pthread_create(&t4,NULL, sell_ticket,(void*)"thread 4");pthread_join(t1,NULL);pthread_join(t2,NULL);pthread_join(t3,NULL);pthread_join(t4,NULL);pthread_mutex_destroy(&mutex);return0;}

运行结果:无负数票,每个线程独占临界区,数据一致。

在这里插入图片描述


2.3 RAII风格锁封装:避免锁泄漏

手动加锁/解锁容易出现“忘记解锁”或“异常时未解锁”的问题,采用RAII(资源获取即初始化)风格封装锁,利用C++析构函数自动释放锁:

// Lock.hpp#pragmaonce#include<pthread.h>namespace LockModule {classMutex{public:Mutex(){pthread_mutex_init(&_mutex,NULL);}~Mutex(){pthread_mutex_destroy(&_mutex);}voidLock(){pthread_mutex_lock(&_mutex);}voidUnlock(){pthread_mutex_unlock(&_mutex);} pthread_mutex_t*GetRawMutex(){return&_mutex;}private:Mutex(const Mutex&)=delete;// 禁止拷贝 Mutex&operator=(const Mutex&)=delete; pthread_mutex_t _mutex;};// RAII锁,构造加锁,析构解锁classLockGuard{public:LockGuard(Mutex& mutex):_mutex(mutex){ _mutex.Lock();}~LockGuard(){ _mutex.Unlock();}private:LockGuard(const LockGuard&)=delete; LockGuard&operator=(const LockGuard&)=delete; Mutex& _mutex;};}

使用改进

// test.cpp#include"Lock.hpp"#include<stdio.h>#include<unistd.h>usingnamespace LockModule;int ticket =100; Mutex mutex;void*sell_ticket(void* arg){char* id =(char*)arg;while(1){// 先获取数据,尽快释放锁int current_ticket =0;{ LockGuard lock(mutex);if(ticket <=0)break; current_ticket = ticket; ticket--;}// 锁在这里就释放了// 模拟耗时操作usleep(1000);printf("%s sells ticket:%d\n", id, current_ticket);}}intmain(){ pthread_t t1, t2, t3, t4;pthread_create(&t1,NULL, sell_ticket,(void*)"thread 1");pthread_create(&t2,NULL, sell_ticket,(void*)"thread 2");pthread_create(&t3,NULL, sell_ticket,(void*)"thread 3");pthread_create(&t4,NULL, sell_ticket,(void*)"thread 4");pthread_join(t1,NULL);pthread_join(t2,NULL);pthread_join(t3,NULL);pthread_join(t4,NULL);return0;}
在这里插入图片描述


三、线程同步:条件变量(cond)实现有序协作

互斥量解决了“同一时间只有一个线程访问资源”,但无法解决“线程按顺序访问”的问题。例如上面的售票系统输出顺序我们是无法控制的,此时需要条件变量

3.1 同步的意义:解决“竞态条件”

  • 竞态条件:因线程执行时序不确定导致程序异常(如消费者先消费空队列);
  • 同步:在保证互斥的基础上,让线程按预期顺序执行,避免竞态条件。

3.2 条件变量接口与核心原理

条件变量允许线程在某个条件不满足时主动等待,当条件满足时被其他线程唤醒。它本身不保证互斥,需与互斥量配合使用,核心接口如下:

接口功能关键说明
pthread_cond_init动态初始化条件变量attr=NULL使用默认属性
PTHREAD_COND_INITIALIZER静态初始化条件变量全局或静态条件变量使用
pthread_cond_wait等待条件满足1. 自动释放互斥量;2. 被唤醒后重新获取互斥量
pthread_cond_signal唤醒一个等待线程唤醒等待队列中的一个线程
pthread_cond_broadcast唤醒所有等待线程避免线程饥饿
pthread_cond_destroy销毁条件变量静态初始化的无需销毁

为什么pthread_cond_wait需要互斥量?

  1. 条件的判断和修改依赖共享资源(如队列是否为空),需互斥保护;
  2. 解锁和等待必须是原子操作:若先解锁再等待,可能错过其他线程的信号,导致永久阻塞。pthread_cond_wait内部自动完成“解锁→等待→唤醒后加锁”的原子流程。

3.3 条件变量使用规范

等待条件:必须用while循环,而非if,避免伪唤醒(线程被唤醒但条件未满足):

pthread_mutex_lock(&mutex);while(条件不满足){// 如队列空、队列满pthread_cond_wait(&cond,&mutex);}// 处理临界资源pthread_mutex_unlock(&mutex);

发送信号:修改条件后发送信号,且需在互斥量保护内:

pthread_mutex_lock(&mutex); 修改条件;// 如向队列添加元素、移除元素pthread_cond_signal(&cond);pthread_mutex_unlock(&mutex);

3.4 条件变量封装

// Cond.hpp#pragmaonce#include"Lock.hpp"#include<pthread.h>namespace CondModule {usingnamespace LockModule;classCond{public:Cond(){pthread_cond_init(&_cond,NULL);}~Cond(){pthread_cond_destroy(&_cond);}voidWait(Mutex& mutex){// 传入互斥量,解耦pthread_cond_wait(&_cond, mutex.GetRawMutex());}voidSignal(){pthread_cond_signal(&_cond);}voidBroadcast(){pthread_cond_broadcast(&_cond);}private:Cond(const Cond&)=delete; Cond&operator=(const Cond&)=delete; pthread_cond_t _cond;};}
// test.cpp#include"Cond.hpp"#include<stdio.h>#include<unistd.h>usingnamespace LockModule;usingnamespace CondModule;int ticket =100; Mutex mutex; Cond cond;// 当前应该执行的线程ID(1,2,3,4)int current_thread_id =1;void*sell_ticket(void* arg){int my_id =*(int*)arg;char id_str[20];sprintf(id_str,"thread %d", my_id);while(1){{ LockGuard lock(mutex);// 等待轮到自己执行while(current_thread_id != my_id && ticket >0){ cond.Wait(mutex);}// 检查票是否卖完if(ticket <=0){// 通知下一个线程,让它也退出 current_thread_id =(current_thread_id %4)+1; cond.Broadcast();// 唤醒所有等待的线程break;}// 卖票int current_ticket = ticket; ticket--;// 更新下一个应该执行的线程 current_thread_id =(current_thread_id %4)+1;// 打印售票信息(在锁内打印确保顺序)printf("%s sells ticket:%d\n", id_str, current_ticket);// 通知下一个线程 cond.Broadcast();// 唤醒所有等待的线程}// 在锁外模拟耗时操作usleep(1000);}returnNULL;}intmain(){ pthread_t t1, t2, t3, t4;int thread_ids[4]={1,2,3,4};pthread_create(&t1,NULL, sell_ticket,(void*)&thread_ids[0]);pthread_create(&t2,NULL, sell_ticket,(void*)&thread_ids[1]);pthread_create(&t3,NULL, sell_ticket,(void*)&thread_ids[2]);pthread_create(&t4,NULL, sell_ticket,(void*)&thread_ids[3]);pthread_join(t1,NULL);pthread_join(t2,NULL);pthread_join(t3,NULL);pthread_join(t4,NULL);return0;}

这样我们就利用了条件变量实现了售票的同步。

在这里插入图片描述


四、生产者消费者模型:同步互斥的经典实战

生产者消费者模型是同步互斥的典型应用,核心是“解耦生产者和消费者,平衡处理能力”,遵循321原则

  • 3种关系:生产者与生产者(互斥)、消费者与消费者(互斥)、生产者与消费者(同步+互斥);
  • 2个角色:生产者(生产数据)、消费者(消费数据);
  • 1个容器:阻塞队列/环形队列(存储数据,解耦角色)。

4.1 模型核心价值

  1. 解耦:生产者和消费者无需知道对方存在,仅通过容器交互;
  2. 支持并发:多个生产者/消费者可同时工作;
  3. 平衡负载:生产者生产快时,容器缓存数据;消费者消费快时,容器空则阻塞等待。

4.2 实现一:基于阻塞队列(动态大小)

阻塞队列是动态大小的容器,特点:

  • 队列为空时,消费者阻塞;
  • 队列满时(可选限制),生产者阻塞;

基于std::queue+互斥量+条件变量实现。

在这里插入图片描述
完整代码(BlockQueue.hpp)
#pragmaonce#include<queue>#include"Lock.hpp"#include"Cond.hpp"namespace Model {usingnamespace LockModule;usingnamespace CondModule;template<typenameT>classBlockQueue{public:BlockQueue(int cap =10):_cap(cap),_stop(false){}~BlockQueue(){// 析构时停止队列,唤醒所有阻塞线程Stop();}// 停止队列voidStop(){ LockGuard lock(_mutex); _stop =true;// 广播所有条件变量,唤醒所有阻塞线程 _prod_cond.Broadcast(); _cons_cond.Broadcast();}// 生产者入队:返回是否成功(如果队列已停止,返回 false)boolEnqueue(const T& data){ LockGuard lock(_mutex);// 队列满且未停止时,阻塞等待while(_queue.size()>= _cap &&!_stop){ _prod_cond.Wait(_mutex);}// 如果队列已停止,直接返回失败if(_stop){returnfalse;}// 生产数据 _queue.push(data);// 唤醒一个等待的消费者(队列非空了) _cons_cond.Signal();returntrue;}// 消费者出队:返回是否成功(如果队列已停止且为空,返回 false)boolDequeue(T& data){ LockGuard lock(_mutex);// 队列为空且未停止时,阻塞等待while(_queue.empty()&&!_stop){ _cons_cond.Wait(_mutex);}// 如果队列已停止且为空,返回失败if(_stop && _queue.empty()){returnfalse;}// 消费数据 data = _queue.front(); _queue.pop();// 唤醒一个等待的生产者(队列未满了) _prod_cond.Signal();returntrue;}// 判断队列是否已停止(可选接口)boolIsStopped()const{ LockGuard lock(_mutex);return _stop;} size_t Size(){ LockGuard lock(_mutex);return _queue.size();}private: std::queue<T> _queue;// 任务队列int _cap;// 队列最大容量 Mutex _mutex;// 保护队列的互斥量 Cond _prod_cond;// 生产者条件变量(队列不满) Cond _cons_cond;// 消费者条件变量(队列不空)bool _stop;// 退出标志(是否停止队列)};}
测试代码
#include"BlockQueue.hpp"// 包含修复后的阻塞队列实现#include<pthread.h>#include<iostream>#include<unistd.h>#include<string>usingnamespace Model;usingnamespace std;// 全局阻塞队列(容量设为 3,方便观察阻塞效果) BlockQueue<string>g_task_queue(3);// 生产者线程函数:生成 10 个任务,之后退出void*ProducerThread(void* arg){constchar* producer_name =static_cast<constchar*>(arg); cout <<"["<< producer_name <<"] 启动,开始生产任务..."<< endl;for(int i =0; i <10;++i){// 生成任务(模拟任务数据) string task ="任务-"+to_string(i);// 入队(队列满时会阻塞)bool ret = g_task_queue.Enqueue(task);if(!ret){ cout <<"["<< producer_name <<"] 队列已停止,停止生产"<< endl;break;} cout <<"["<< producer_name <<"] 生产任务:"<< task <<" | 队列当前大小:"<< g_task_queue.Size()<< endl;// 模拟生产耗时(1秒/个)sleep(1);} cout <<"["<< producer_name <<"] 生产完毕,退出线程"<< endl;returnnullptr;}// 消费者线程函数:持续消费任务,直到队列停止且为空void*ConsumerThread(void* arg){constchar* consumer_name =static_cast<constchar*>(arg); cout <<"["<< consumer_name <<"] 启动,开始消费任务..."<< endl;while(true){ string task;// 出队(队列为空时会阻塞)bool ret = g_task_queue.Dequeue(task);if(!ret){ cout <<"["<< consumer_name <<"] 队列已停止且无任务,退出消费"<< endl;break;} cout <<"["<< consumer_name <<"] 消费任务:"<< task <<" | 队列当前大小:"<< g_task_queue.Size()<< endl;// 模拟消费耗时(2秒/个,故意比生产慢,触发队列满阻塞生产者)sleep(2);} cout <<"["<< consumer_name <<"] 消费完毕,退出线程"<< endl;returnnullptr;}intmain(){ pthread_t producer, consumer;constchar* prod_name ="生产者";constchar* cons_name ="消费者";// 创建生产者和消费者线程pthread_create(&producer,nullptr, ProducerThread,(void*)prod_name);pthread_create(&consumer,nullptr, ConsumerThread,(void*)cons_name);// 主线程等待 20 秒(确保生产者完成所有任务,消费者消费完毕) cout <<"主线程:等待 20 秒后停止队列..."<< endl;sleep(20);// 停止队列(优雅唤醒阻塞的线程) cout <<"主线程:停止队列"<< endl; g_task_queue.Stop();// 等待线程退出(回收资源)pthread_join(producer,nullptr);pthread_join(consumer,nullptr); cout <<"主线程:所有线程退出,程序结束"<< endl;return0;}
在这里插入图片描述


4.3 实现二:基于环形队列(固定大小)

环形队列是固定大小的数组,用模运算实现“环形”,特点:

  • 无需动态扩容,性能更优;
  • 用信号量实现同步,互斥量实现生产者/消费者内部互斥;
  • 适合对性能要求高、数据量可预估的场景。
核心思路
  • 信号量_empty:表示空闲空间数,初始为队列容量;
  • 信号量_full:表示已用空间数,初始为0;
  • 生产者:P(_empty)→生产→V(_full)
  • 消费者:P(_full)→消费→V(_empty)

互斥量_prod_mutex/_cons_mutex:保证多个生产者/消费者的互斥访问。

在这里插入图片描述
完整代码(RingQueue.hpp)
#pragmaonce#include<vector>#include<pthread.h>#include<semaphore.h>namespace Model {template<typenameT>classRingQueue{public:RingQueue(int cap =10):_cap(cap),_prod_idx(0),_cons_idx(0){ _queue.resize(_cap);// 初始化信号量sem_init(&_empty,0, _cap);// 0表示线程间共享sem_init(&_full,0,0);// 初始化互斥量pthread_mutex_init(&_prod_mutex,NULL);pthread_mutex_init(&_cons_mutex,NULL);}~RingQueue(){sem_destroy(&_empty);sem_destroy(&_full);pthread_mutex_destroy(&_prod_mutex);pthread_mutex_destroy(&_cons_mutex);}// 生产者入队voidEnqueue(const T& data){sem_wait(&_empty);// P操作:空闲空间-1,无空间则阻塞pthread_mutex_lock(&_prod_mutex);// 多个生产者互斥// 生产数据 _queue[_prod_idx]= data; _prod_idx =(_prod_idx +1)% _cap;// 环形索引pthread_mutex_unlock(&_prod_mutex);sem_post(&_full);// V操作:已用空间+1,唤醒消费者}// 消费者出队boolDequeue(T& data){sem_wait(&_full);// P操作:已用空间-1,无数据则阻塞pthread_mutex_lock(&_cons_mutex);// 多个消费者互斥// 消费数据 data = _queue[_cons_idx]; _cons_idx =(_cons_idx +1)% _cap;pthread_mutex_unlock(&_cons_mutex);sem_post(&_empty);// V操作:空闲空间+1,唤醒生产者returntrue;}private: std::vector<T> _queue;// 环形队列数组int _cap;// 固定容量int _prod_idx;// 生产者索引int _cons_idx;// 消费者索引 sem_t _empty;// 空闲空间信号量 sem_t _full;// 已用空间信号量 pthread_mutex_t _prod_mutex;// 生产者互斥量 pthread_mutex_t _cons_mutex;// 消费者互斥量};}

测试代码:

#include<iostream>#include<unistd.h>// 用于sleep#include<cstdlib>// 用于rand、srand#include<ctime>// 用于time(设置随机数种子)#include"RingQueue.hpp"// 你的环形队列头文件#include"Lock.hpp"// 你的互斥锁头文件#include"Cond.hpp"// 你的条件变量头文件usingnamespace std;usingnamespace Model;// 测试数据量(可调整)constint TEST_DATA_COUNT =20;// 生产者线程函数:向环形队列中写入数据void*ProducerThread(void* arg){ RingQueue<int>* rq =static_cast<RingQueue<int>*>(arg);srand(time(nullptr));// 设置随机数种子(仅生产者设置一次即可)for(int i =0; i < TEST_DATA_COUNT;++i){// 生成1~100的随机数作为测试数据int data =rand()%100+1;// 入队 rq->Enqueue(data); cout <<"[生产者] 写入数据: "<< data << endl;// 模拟生产耗时(100~300毫秒),让测试更直观usleep(rand()%200000+100000);} cout <<"[生产者] 数据写入完成,退出线程"<< endl;pthread_exit(nullptr);}// 消费者线程函数:从环形队列中读取数据void*ConsumerThread(void* arg){ RingQueue<int>* rq =static_cast<RingQueue<int>*>(arg);int data =0;for(int i =0; i < TEST_DATA_COUNT;++i){// 出队bool ret = rq->Dequeue(data);if(ret){ cout <<"[消费者] 读取数据: "<< data << endl;}else{ cout <<"[消费者] 出队失败!"<< endl;}// 模拟消费耗时(150~400毫秒),与生产耗时错开,体现同步usleep(rand()%250000+150000);} cout <<"[消费者] 数据读取完成,退出线程"<< endl;pthread_exit(nullptr);}intmain(){// 创建环形队列(容量设为5,测试环形特性) RingQueue<int>ring_queue(5); pthread_t prod_tid, cons_tid;int ret =0;// 创建生产者线程 ret =pthread_create(&prod_tid,nullptr, ProducerThread,&ring_queue);if(ret !=0){ cerr <<"创建生产者线程失败!错误码: "<< ret << endl;return-1;}// 创建消费者线程 ret =pthread_create(&cons_tid,nullptr, ConsumerThread,&ring_queue);if(ret !=0){ cerr <<"创建消费者线程失败!错误码: "<< ret << endl;return-1;}// 等待两个线程结束 ret =pthread_join(prod_tid,nullptr);if(ret !=0){ cerr <<"等待生产者线程失败!错误码: "<< ret << endl;return-1;} ret =pthread_join(cons_tid,nullptr);if(ret !=0){ cerr <<"等待消费者线程失败!错误码: "<< ret << endl;return-1;} cout <<"\n===== 单生产者单消费者测试完成 ====="<< endl;return0;}
在这里插入图片描述

4.4 两种模型对比与选型

特性阻塞队列(动态)环形队列(固定)
空间大小动态扩容,无固定限制固定大小,需提前预估
实现方式互斥量+条件变量互斥量+信号量
性能扩容时有开销,中等无扩容开销,高性能
适用场景数据量不确定,追求灵活性数据量稳定,追求高并发

五、线程安全与可重入问题

线程安全和可重入是多线程编程中容易混淆的概念,需明确区分:

5.1 线程安全:多线程访问的一致性保障

  • 定义:多个线程并发访问同一资源时,结果始终符合预期,无数据竞争;
  • 线程安全的场景
    • 仅访问局部变量(每个线程有独立栈空间);
    • 访问共享资源但有互斥保护(如加锁);
    • 仅读取共享资源(无写入操作);
  • 线程不安全的场景
    • 无保护地修改全局变量/静态变量;
    • 函数状态随调用变化(如返回静态变量指针);
    • 调用线程不安全的函数(如strtok)。

5.2 可重入函数:重入场景下的正确性

  • 定义:同一函数被多个执行流(线程/信号处理函数)重入时,运行结果正确;
  • 可重入的场景
    • 不使用全局变量/静态变量;
    • 不调用malloc/free(依赖全局堆管理);
    • 不调用标准I/O库函数(依赖全局缓冲区);
    • 所有数据来自参数或局部变量;
  • 不可重入的场景
    • 使用全局变量/静态变量;
    • 调用不可重入函数(如mallocprintf);
    • 返回静态变量指针(如char* get_str() { static char buf[1024]; return buf; })。

5.3 核心区别与联系

维度线程安全可重入
关注对象多线程并发访问的资源一致性函数自身的重入安全性
依赖条件可能依赖互斥锁不依赖任何共享资源
关系可重入函数一定是线程安全的线程安全函数不一定是可重入的
示例加锁的全局变量操作仅使用局部变量的函数

六、死锁

死锁是多线程编程的常见问题,指多个线程互相持有对方需要的锁,且均不释放,导致永久阻塞。

6.1 死锁的四个必要条件

  1. 互斥条件:资源只能被一个线程持有;
  2. 请求与保持条件:线程持有部分资源,同时请求其他线程的资源;
  3. 不剥夺条件:资源不能被强行剥夺,只能由持有者主动释放;
  4. 循环等待条件:线程间形成“线程A(持lock1,等lock2)→ 线程B(持lock2,等lock3)→ 线程C(持lock3,等lock1)→ 线程A”的循环。

6.2 避免死锁的实战方法

方法1:破坏循环等待条件(最常用)
  • 给锁编号,所有线程按统一顺序加锁;
  • 示例:线程A和B都需要锁1和锁2,约定先加锁1,再加锁2:
// 正确:按顺序加锁pthread_mutex_lock(&lock1);pthread_mutex_lock(&lock2);// 操作资源pthread_mutex_unlock(&lock2);pthread_mutex_unlock(&lock1);
方法2:破坏请求与保持条件
  • 一次性申请所有需要的资源,申请失败则释放已申请的资源;
  • 示例:使用pthread_mutex_trylock尝试加锁,失败则回滚:
if(pthread_mutex_lock(&lock1)!=0){return-1;}if(pthread_mutex_trylock(&lock2)!=0){pthread_mutex_unlock(&lock1);// 回滚已加的锁return-1;}
方法3:设置锁超时
  • 使用pthread_mutex_timedlock,超时则释放锁并重试;
  • 避免线程永久阻塞。
方法4:破坏不剥夺条件
  • 允许线程在超时后释放已持有的锁(需配合业务逻辑设计)。

七、总结

  1. 互斥:用互斥量(mutex)保护临界区,RAII封装避免锁泄漏;
  2. 同步:用条件变量(cond)实现线程有序协作,必须配合互斥量;
  3. 生产者消费者模型:阻塞队列(灵活)和环形队列(高性能),解耦并发角色;
  4. 线程池:预创建线程,避免线程频繁创建销毁,提升高并发性能;
  5. 避坑:区分线程安全与可重入,避免死锁。

Read more

OpenClaw-多飞书机器人与多Agent团队实战复盘

OpenClaw-多飞书机器人与多Agent团队实战复盘

OpenClaw 多飞书机器人与多 Agent 团队实战复盘 这篇文章完整记录一次从单机安装到多机器人协作落地的真实过程: 包括 Windows 安装报错、Gateway 连通、模型切换、Feishu 配对、多 Agent 路由、身份错位修复,以及最终形成“产品-开发-测试-评审-文档-运维”团队。 一、目标与结果 这次实践的目标很明确: 1. 在 Windows 上稳定跑通 OpenClaw 2. 接入飞书机器人 3. 做到一个机器人对应一个 Agent 角色 4. 支持多模型并行(OpenAI + Ollama) 5. 最终形成可执行的多 Agent 团队 最终落地状态(已验证): * 渠道:Feishu 多账号在线 * 路由:按 accountId

By Ne0inhk

6G显存就能玩转2K AI绘画:腾讯混元Image-2.1 GGUF版深度体验

腾讯混元Image-2.1 GGUF版本的发布,标志着AI绘画技术正式进入"全民时代"。这个突破性的轻量化方案让普通用户也能在消费级显卡上体验专业级图像生成,将显存需求从原来的24GB大幅降低至6GB级别,同时保持80-90%的原始图像质量。现在,你只需一台配备RTX 3060级别显卡的普通电脑,就能创作出令人惊艳的2K分辨率数字艺术作品。 【免费下载链接】hunyuanimage-gguf 项目地址: https://ai.gitcode.com/hf_mirrors/calcuis/hunyuanimage-gguf 🎨 为什么说这是AI绘画的"平民革命"? 过去一年,AI绘画领域最大的痛点就是"硬件门槛过高"。传统生图模型动辄需要12-16GB显存,让众多拥有中端显卡的用户望而却步。腾讯混元团队通过GGUF量化技术,成功解决了这一行业难题。 量化技术的魔力 * Q4_K_S版本:仅需10.5GB存储空间 * Q5_K_M版本:12.8GB存储空间

By Ne0inhk
Formality:原语(primitive)的概念

Formality:原语(primitive)的概念

相关阅读 Formalityhttps://blog.ZEEKLOG.net/weixin_45791458/category_12841971.html?spm=1001.2014.3001.5482         原语(primitive)一般指的是语言内置的基本构件,它们代表了基本的逻辑门和构件,通常用于建模电路的基本功能,例如Verilog中的门级建模会使用and、or等关键词表示单元门。Formality也存在原语的概念,这一般出现在对门级网表进行建模时,本文将对此进行详细解释。         假设以例1所示的RTL代码作为参考设计(可以看出添加了// synopsys sync_set_reset综合指令让Design Compiler将其实现为带同步复位端的D触发器),例2所示的综合后网表作为实现设计,其中data_out_reg原语是一个带同步复位端的D触发器(FDS2)。 // 例1 module ref( input clk, input reset, input data_in, output reg data_

By Ne0inhk
【OpenHarmony】鸿蒙Flutter智能家居应用开发实战指南

【OpenHarmony】鸿蒙Flutter智能家居应用开发实战指南

鸿蒙Flutter智能家居应用开发实战指南 概述 智能家居是鸿蒙全场景生态的重要应用场景。本文讲解如何基于鸿蒙Flutter框架,开发一套完整的智能家居应用,实现设备发现、控制、场景联动、语音交互等核心功能。 欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net 系统架构设计 整体架构图 ┌────────────────────────────────────────────────────────────┐ │ 用户交互层 (Flutter) │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ 设备控制面板 │ │ 场景编排 │ │ 语音交互 │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ └───────────────────────┬────────────────────────────────────┘ │ RPC/事件总线 ┌────────────────────

By Ne0inhk