解码死锁的产生与解决

死锁

死锁是多线程 / 多进程并发编程中常见的严重问题,指两个或多个线程 / 进程因互相争抢资源而陷入无限等待状态,若无外力干预将永久无法继续执行。其本质是资源分配与调度不当导致的 "资源僵局",典型现实类比是 "哲学家就餐问题":五位哲学家围坐圆桌,每人需拿起左右两根筷子才能吃饭,若所有哲学家同时拿起左侧筷子,将永远等待右侧筷子,最终全部陷入停滞。

死锁产生的四个必要条件

死锁的发生必须同时满足以下四个条件,缺一不可,这是理解和解决死锁的核心基础:

  • 资源互斥:资源在同一时间只能被一个线程 / 进程占用,无法被多个线程 / 进程同时使用(如互斥锁、打印机等硬件资源)。

  • 请求且保持:线程 / 进程已持有部分资源,又主动申请新的资源,且在申请新资源的过程中,不释放已持有的资源。

  • 不可剥夺:已被线程 / 进程占用的资源,不能被强行剥夺,只能由资源持有者主动释放。

  • 循环等待:多个线程 / 进程之间形成头尾相接的环形等待链,每个线程 / 进程都在等待下一个线程 / 进程持有的资源(如线程 A 等线程 B 的资源,线程 B 等线程 A 的资源)。

死锁的预防策略(从根源杜绝死锁)

预防死锁的核心思路是破坏上述四个必要条件中的至少一个,从设计阶段杜绝死锁发生,是最有效且常用的方案:

破坏 "请求且保持" 条件

  • 核心方案:一次性申请所有资源。线程 / 进程在启动前,需一次性申请执行过程中所需的全部资源,若无法完全满足则放弃所有申请,进入等待状态,待资源充足时再重新申请。
  • 特点:实现简单,能彻底避免 "请求且保持" 导致的死锁,但资源利用率极低,可能造成大量资源闲置。

破坏 "不可剥夺" 条件

  • 核心方案:引入资源抢占机制。若线程 / 进程持有部分资源后,申请新资源失败,需主动释放已持有的所有资源,后续需重新申请全部资源(包括之前释放的)。
  • 实操方式:结合超时机制实现,使用带超时的资源申请函数(如pthread_mutex_timedlock),超时未获取新资源时,自动释放已持有资源,避免长期占用资源导致死锁。
  • 特点:需额外处理资源释放与重新申请的逻辑,增加编程复杂度和上下文切换开销。

破坏 "循环等待" 条件(最常用、最实用)

  • 核心方案:资源有序分配法。给所有资源(如锁、设备等)定义统一的优先级或序号,所有线程 / 进程必须按照 "递增顺序" 申请资源,禁止逆序申请。
  • 原理:强制统一的申请顺序后,线程 / 进程间无法形成环形等待链。例如给锁 L1、L2、L3 编号为 1、2、3,所有线程必须先申请 L1,再申请 L2,最后申请 L3,避免 A 等 B、B 等 A 的环路。
  • 特点:实现成本低、资源利用率高,是实际开发中首选的预防方案。

死锁的避免策略(运行时动态规避)

避免死锁不提前限制资源申请方式,而是在资源分配时动态判断,避免系统进入 "不安全状态"(可能发生死锁的状态):

  • 核心算法:银行家算法。系统维护所有资源的分配状态和进程的最大资源需求,每次分配资源前,先模拟分配过程,若分配后系统仍处于安全状态(存在一条进程执行序列,可让所有进程顺利完成)则分配,否则拒绝分配。
  • 特点:资源利用率高于预防策略,但存在明显局限性 ------ 需提前预知所有进程的最大资源需求(实际开发中难以做到),且算法本身存在计算开销,仅适用于操作系统等底层系统设计,通用应用开发中极少使用。

死锁的检测与处理(事后解决方案)

该策略放任死锁发生,通过定期检测发现死锁后,采取措施打破僵局,适用于无法提前预防或避免的场景:

死锁检测

  • 核心机制:维护资源分配图。系统记录所有资源的分配关系(哪个进程持有哪个资源)和申请关系(哪个进程申请哪个资源),形成有向图,定期调用环路检测算法,若图中存在环路,则判定发生死锁。
  • 实操方式(Linux 系统):通过/proc/locks文件查看所有持有锁的进程 ID 和锁信息,命令:cat /proc/locks;也可使用ipcs -s查看 System V 信号量相关的进程资源占用。

死锁处理

  • 进程终止法:
    • 终止所有死锁进程:简单直接,但代价极高,可能导致数据丢失和任务中断。
    • 逐个终止进程:每次终止一个 "代价最小" 的进程(如优先级最低、已运行时间最短的进程),释放其资源后重新检测,直到死锁解除。
  • 资源抢占法:
    • 从死锁进程中抢占资源,分配给其他死锁进程,直到打破等待环路。需解决三个问题:选择哪个进程作为牺牲者(最小化损失)、如何回滚进程状态(抢占资源后,进程需回滚到未获取该资源的安全状态)、避免饥饿(防止同一进程反复被抢占)。

死锁场景代码示例(错误与修正)

死锁场景(错误代码)

两个线程因加锁顺序不一致,形成循环等待,最终触发死锁:

c 复制代码
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
// 定义两个全局互斥锁
pthread_mutex_t lockA = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t lockB = PTHREAD_MUTEX_INITIALIZER;

// 线程1:先获取lockA,再获取lockB
void *thread1(void *arg) {
    // 加锁lockA,成功则持有该锁
    pthread_mutex_lock(&lockA);
    printf("线程1:持有lockA,尝试获取lockB\n");
    // 睡眠1秒,让线程2有机会获取lockB,制造循环等待条件
    sleep(1);
    
    // 尝试获取lockB,此时线程2已持有lockB,陷入等待
    pthread_mutex_lock(&lockB); // 死锁触发点:等待线程2的lockB
    printf("线程1:成功获取lockB\n");
    
    // 解锁操作(死锁后无法执行到此处)
    pthread_mutex_unlock(&lockB);
    pthread_mutex_unlock(&lockA);
    return NULL;
}

// 线程2:先获取lockB,再获取lockA(与线程1加锁顺序相反)
void *thread2(void *arg) {
    // 加锁lockB,成功则持有该锁
    pthread_mutex_lock(&lockB);
    printf("线程2:持有lockB,尝试获取lockA\n");
    // 睡眠1秒,让线程1有机会获取lockA,制造循环等待条件
    sleep(1);
    
    // 尝试获取lockA,此时线程1已持有lockA,陷入等待
    pthread_mutex_lock(&lockA); // 死锁触发点:等待线程1的lockA
    printf("线程2:成功获取lockA\n");
    
    // 解锁操作(死锁后无法执行到此处)
    pthread_mutex_unlock(&lockA);
    pthread_mutex_unlock(&lockB);
    return NULL;
}

int main() {
    pthread_t tid1, tid2;
    // 创建线程1
    pthread_create(&tid1, NULL, thread1, NULL);
    // 创建线程2
    pthread_create(&tid2, NULL, thread2, NULL);
    
    // 等待线程结束(死锁后永久阻塞)
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    return 0;
}

修正方案:统一加锁顺序(破坏循环等待条件)

将所有线程的加锁顺序统一为 "先 lockA 后 lockB",避免循环等待,从根源解决死锁:

c 复制代码
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
pthread_mutex_t lockA = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t lockB = PTHREAD_MUTEX_INITIALIZER;

// 线程1:保持先lockA后lockB的顺序
void *thread1(void *arg) {
    pthread_mutex_lock(&lockA);
    printf("线程1:持有lockA,尝试获取lockB\n");
    sleep(1);
    pthread_mutex_lock(&lockB);
    printf("线程1:成功获取lockB\n");
    
    // 业务操作完成后,按加锁逆序解锁
    pthread_mutex_unlock(&lockB);
    pthread_mutex_unlock(&lockA);
    return NULL;
}

// 线程2:修改为与线程1一致的加锁顺序(先lockA后lockB)
void *thread2(void *arg) {
    pthread_mutex_lock(&lockA); // 统一先加lockA
    printf("线程2:持有lockA,尝试获取lockB\n");
    sleep(1);
    pthread_mutex_lock(&lockB); // 再加lockB
    printf("线程2:成功获取lockB\n");
    
    // 按加锁逆序解锁,避免资源泄漏
    pthread_mutex_unlock(&lockB);
    pthread_mutex_unlock(&lockA);
    return NULL;
}

int main() {
    pthread_t tid1, tid2;
    pthread_create(&tid1, NULL, thread1, NULL);
    pthread_create(&tid2, NULL, thread2, NULL);
    
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    return 0;
}

线程取消导致的死锁及解决方案

线程执行过程中被取消(如调用pthread_cancel)时,若已持有资源(如锁)且未释放,会导致其他线程永久等待该资源,引发死锁。需通过特定机制确保线程取消时释放所有资源:

方案 1:使用取消处理函数(自动释放资源)

通过pthread_cleanup_pushpthread_cleanup_pop函数,在线程被取消时自动执行资源释放操作,确保资源不泄露:

函数解析

c 复制代码
#include <pthread.h>

/**
 * 线程取消请求发送函数(POSIX标准接口)
 * @brief 向指定线程发送取消请求,请求线程终止执行,是线程间主动终止目标线程的核心接口
 * @param thread 目标线程的ID(由pthread_create创建线程时输出参数获取)
 * @return int 返回值含义:
 *              - 0:取消请求发送成功(仅表示请求已递交,不代表线程已终止);
 *              - ESRCH:目标线程ID不存在(如线程已退出、ID无效);
 *              - EINVAL:线程取消机制被禁用(目标线程通过pthread_setcancelstate设置为PTHREAD_CANCEL_DISABLE)
 * @note 取消响应特性:线程是否响应取消、何时响应,由目标线程的取消状态和类型决定,非立即终止;
 *       取消状态控制:目标线程需处于PTHREAD_CANCEL_ENABLE状态(默认启用)才能接收取消请求,禁用状态下请求会被忽略;
 *       取消类型影响:
 *           - PTHREAD_CANCEL_DEFERRED(默认):延迟响应,线程仅在执行到"取消点"时才处理取消请求(如sleep、read、pthread_join等系统调用);
 *           - PTHREAD_CANCEL_ASYNCHRONOUS:立即响应,无需等待取消点,收到请求后尽快终止(需谨慎使用,可能导致资源泄漏);
 *       资源泄漏风险:若目标线程持有锁、动态内存等资源时被取消,且未设置清理函数,会导致资源永久占用(死锁诱因之一);
 *       线程退出状态:被取消的线程退出状态为PTHREAD_CANCELED(宏定义为(void*)-1),可通过pthread_join的第二个参数获取;
 *       不可取消场景:线程执行某些关键操作(如内核态代码、原子操作)时,可能暂时无法响应取消,需等待操作完成;
 *       与pthread_exit区别:pthread_exit是线程主动终止,pthread_cancel是外部线程发起的被动终止请求
 */
int pthread_cancel(pthread_t thread);

/**
 * 线程取消清理函数压栈函数(POSIX标准接口)
 * @brief 将指定的清理函数压入当前线程的"取消清理栈",线程被取消或执行pthread_cleanup_pop(非0)时,自动执行该函数
 * @param routine 取消清理函数指针,函数原型必须为void (*routine)(void *),线程触发清理时自动调用,参数为arg
 * @param arg 传递给清理函数routine的参数(可传递锁地址、内存指针等需释放的资源标识)
 * @return 无返回值
 * @note 栈特性:清理栈遵循"后进先出(LIFO)"原则,最后压入的函数最先执行;
 *       配对使用要求:必须与pthread_cleanup_pop函数成对出现,且在同一代码块(如同一函数、同一循环体)中,否则编译报错;
 *       压栈时机:需在获取资源(如加锁、分配内存)之前压入对应清理函数,确保资源持有期间触发取消时能正确释放;
 *       执行触发条件:
 *           - 线程被pthread_cancel取消且响应成功;
 *           - 线程调用pthread_exit主动退出;
 *           - 调用pthread_cleanup_pop时参数为非0值(无论是否取消,均执行弹出的清理函数);
 *       正常执行场景:若线程未被取消且正常执行到pthread_cleanup_pop(0),则仅弹出清理函数,不执行;
 *       嵌套使用:支持多次压栈多个清理函数,适用于多资源持有场景(如同时持有多个锁,需按逆序释放)
 */
void pthread_cleanup_push(void (*routine)(void *), void *arg);

/**
 * 线程取消清理函数出栈函数(POSIX标准接口)
 * @brief 将当前线程"取消清理栈"顶部的清理函数弹出,根据参数决定是否执行该函数
 * @param execute 执行控制参数:
 *                - 0:仅弹出清理函数,不执行(适用于线程正常执行完成、无需清理资源的场景);
 *                - 非0:弹出清理函数的同时,立即执行该函数(适用于线程主动退出前、需手动触发资源清理的场景)
 * @return 无返回值
 * @note 与push的绑定关系:必须与pthread_cleanup_push一一对应,数量不匹配会导致编译链接错误(编译器通过宏检查配对);
 *       执行时机影响:参数非0时,无论线程是否被取消,都会执行弹出的清理函数,可用于主动释放资源(如函数退出前统一清理);
 *       资源释放顺序:若多次嵌套push,pop时按逆序执行(最后push的函数最先pop并执行),需与资源申请顺序相反(如先申请锁A再申请锁B,清理时先释放B再释放A);
 *       代码块限制:push和pop必须在同一作用域(如{}内),跨函数或跨条件分支的配对会导致编译失败;
 *       与线程退出的配合:线程调用pthread_exit前,若未执行pop(非0),会自动执行清理栈中所有未弹出的清理函数,确保资源释放;
 *       避免重复释放:清理函数中释放的资源(如锁、内存),需确保未被其他代码提前释放,防止双重释放导致程序崩溃
 */
void pthread_cleanup_pop(int execute)

示例

c 复制代码
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
// 互斥锁定义
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

/**
 * @brief 锁资源释放的取消处理函数
 * @param arg 传递的参数(此处为锁的地址)
 * @note 线程被取消时,该函数会自动执行,释放已持有的锁
 */
void lock_cleanup_handler(void *arg) {
    pthread_mutex_t *mutex = (pthread_mutex_t *)arg;
    printf("线程被取消,自动释放锁资源\n");
    pthread_mutex_unlock(mutex); // 释放锁,避免死锁
}

/**
 * @brief 线程执行函数
 * @param arg 线程参数(未使用)
 * @return NULL 线程退出返回值
 */
void *task_func(void *arg) {
    // 加锁前,将解锁函数压入取消处理栈
    // 第一个参数:取消时执行的处理函数地址
    // 第二个参数:传递给处理函数的参数(锁的地址)
    pthread_cleanup_push(lock_cleanup_handler, &lock);
    
    // 获取锁资源
    pthread_mutex_lock(&lock);
    printf("线程获取锁,开始执行业务逻辑\n");
    
    // 模拟耗时操作,期间可能被取消
    while (1) {
        sleep(1);
        printf("线程执行中...\n");
    }
    
    // 正常执行时,弹出取消处理函数且不执行(参数0)
    // 若线程被取消,此处不会执行,栈中的处理函数会自动调用
    pthread_cleanup_pop(0);
    return NULL;
}

int main() {
    pthread_t tid;
    // 创建线程
    pthread_create(&tid, NULL, task_func, NULL);
    
    // 主线程睡眠3秒,让子线程获取锁并执行
    sleep(3);
    // 发送线程取消请求
    printf("主线程发送取消请求\n");
    pthread_cancel(tid);
    
    // 等待线程退出,回收资源
    pthread_join(tid, NULL);
    printf("子线程已退出,资源回收完成\n");
    
    // 销毁互斥锁
    pthread_mutex_destroy(&lock);
    return 0;
}

方案 2:控制线程取消状态与类型

通过pthread_setcancelstatepthread_setcanceltype函数,控制线程对取消请求的响应方式,避免在持有资源时被取消:
函数解析

c 复制代码
#include <pthread.h>

/**
 * 设置线程取消状态(POSIX标准接口)
 * @brief 控制当前线程是否接受外部取消请求(pthread_cancel),是线程取消机制的"总开关"
 * @param state 线程新的取消状态:
 *              - PTHREAD_CANCEL_ENABLE:启用取消机制(默认值),线程可接收并响应取消请求;
 *              - PTHREAD_CANCEL_DISABLE:禁用取消机制,线程会忽略所有取消请求(请求会被挂起,直到重新启用)
 * @param oldstate 输出参数(可为NULL),用于保存线程修改前的旧取消状态,若需后续恢复旧状态可传入有效指针
 * @return int 返回值含义:
 *              - 0:状态设置成功;
 *              - EINVAL:state参数无效(既不是PTHREAD_CANCEL_ENABLE,也不是PTHREAD_CANCEL_DISABLE)
 * @note 核心作用:决定线程是否"允许被取消",与pthread_setcanceltype(取消响应方式)配合使用;
 *       禁用时的请求处理:线程禁用取消期间,外部调用pthread_cancel会返回0(请求递交成功),但线程不会响应,
 *          直到重新启用取消状态,挂起的请求才会被处理(延迟响应场景下需等待取消点);
 *       状态切换的原子性:该函数是原子操作,不会被取消请求打断,确保状态切换的一致性;
 *       与清理函数的配合:禁用取消状态时,即使线程持有资源,也不会因取消请求导致资源泄漏(因请求被忽略);
 *       适用场景:执行关键任务(如数据写入、资源初始化)时,禁用取消避免任务中断;任务完成后重新启用;
 *       线程创建默认值:新创建的线程默认取消状态为PTHREAD_CANCEL_ENABLE,无需手动启用
 */
int pthread_setcancelstate(int state, int *oldstate);

/**
 * 设置线程取消类型(POSIX标准接口)
 * @brief 当线程取消状态为启用(PTHREAD_CANCEL_ENABLE)时,指定线程响应取消请求的方式
 * @param type 线程新的取消类型:
 *              - PTHREAD_CANCEL_DEFERRED:延迟响应(默认值),线程仅在执行到"取消点"时才处理取消请求;
 *              - PTHREAD_CANCEL_ASYNCHRONOUS:立即响应,线程收到取消请求后尽快终止(无需等待取消点)
 * @param oldtype 输出参数(可为NULL),用于保存线程修改前的旧取消类型,若需后续恢复旧类型可传入有效指针
 * @return int 返回值含义:
 *              - 0:类型设置成功;
 *              - EINVAL:type参数无效(既不是PTHREAD_CANCEL_DEFERRED,也不是PTHREAD_CANCEL_ASYNCHRONOUS);
 *              - ENOTSUP:部分系统不支持PTHREAD_CANCEL_ASYNCHRONOUS类型(极少数嵌入式系统)
 * @note 依赖取消状态:仅当线程取消状态为PTHREAD_CANCEL_ENABLE时,取消类型才生效;禁用状态下类型设置无意义;
 *       取消点说明:延迟响应(DEFERRED)模式下,线程仅在执行标准定义的"取消点"时响应取消,常见取消点包括:
 *           - 系统调用:sleep、read、write、pthread_join、pthread_cond_wait、select等;
 *           - 库函数:malloc、free、printf等(部分标准库函数会触发取消点);
 *           - 无取消点场景:纯计算循环(无系统调用/库函数)中,延迟响应的线程可能永远不响应取消(需手动插入pthread_testcancel());
 *       立即响应风险:ASYNCHRONOUS类型可能导致线程在任意时刻终止(如执行临界区代码、分配内存后未初始化时),
 *          极易引发资源泄漏、数据损坏,仅适用于无资源持有、无状态的简单线程;
 *       清理函数兼容性:无论哪种类型,线程被取消时都会执行取消清理栈中的函数(pthread_cleanup_push注册的函数),
 *          但立即响应类型的清理函数执行时机更不确定,需确保清理函数是线程安全的;
 *       与pthread_testcancel配合:延迟响应模式下,若线程执行长时间纯计算(无取消点),可手动调用pthread_testcancel()
 *          插入取消点,让线程有机会响应取消请求;
 *       线程创建默认值:新创建的线程默认取消类型为PTHREAD_CANCEL_DEFERRED,推荐优先使用该类型
 */
int pthread_setcanceltype(int type, int *oldtype);
c 复制代码
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

/**
 * @brief 线程执行函数
 * @param arg 线程参数(未使用)
 * @return NULL 线程退出返回值
 */
void *task_func(void *arg) {
    // 设置线程取消状态为"禁用"(不接受取消请求)
    // 第一个参数:PTHREAD_CANCEL_DISABLE(禁用)/PTHREAD_CANCEL_ENABLE(启用)
    // 第二个参数:保存旧状态的指针(NULL表示不保存)
    pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
    
    // 持有资源执行核心业务(禁用取消,避免中途被取消导致资源泄漏)
    pthread_mutex_lock(&lock);
    printf("线程获取锁,执行核心业务(禁用取消)\n");
    int count = 5;
    while (count--) {
        sleep(1);
        printf("核心业务执行中,剩余%d秒\n", count);
    }
    // 释放资源
    pthread_mutex_unlock(&lock);
    printf("核心业务完成,释放锁资源\n");
    
    // 重新启用取消请求,允许后续被取消
    pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
    
    // 设置取消类型为"立即响应"(无需等待取消点)
    // 第一个参数:PTHREAD_CANCEL_ASYNCHRONOUS(立即响应)/PTHREAD_CANCEL_DEFERRED(延迟响应)
    // 第二个参数:保存旧类型的指针(NULL表示不保存)
    pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
    
    // 后续循环执行,可被立即取消
    while (1) {
        sleep(1);
        printf("非核心业务执行中(允许立即取消)\n");
    }
    
    return NULL;
}

int main() {
    pthread_t tid;
    pthread_create(&tid, NULL, task_func, NULL);
    
    sleep(3);
    printf("主线程发送取消请求\n");
    pthread_cancel(tid);
    
    pthread_join(tid, NULL);
    printf("子线程已退出\n");
    
    pthread_mutex_destroy(&lock);
    return 0;
}

取消点说明

线程取消请求的响应依赖 "取消点"------ 系统定义的函数调用点(如sleepreadpthread_join等,详细查看man手册第七章pthreads)。

延迟响应模式(PTHREAD_CANCEL_DEFERRED)下,线程仅在执行到取消点时才响应取消请求;

立即响应模式(PTHREAD_CANCEL_ASYNCHRONOUS)下,无需等待取消点,可随时响应取消。

死锁预防的其他实用技巧

  • 减少锁的使用范围:仅在必要的临界区使用锁,避免大面积加锁;临界区代码尽量简短,执行完成后立即解锁,减少锁持有时间。
  • 使用带超时的加锁函数 :优先使用pthread_mutex_timedlock(互斥锁超时加锁)、pthread_rwlock_timedwrlock(读写锁写超时加锁)等函数,超时未获取锁时,主动释放已持有资源并重试或退出,避免永久阻塞。
  • 避免嵌套锁:尽量不要在持有一个锁的同时,再申请另一个锁(嵌套加锁),嵌套层级越多,死锁风险越高。
  • 资源复用与池化:通过资源池(如线程池、连接池)管理资源,减少动态资源申请与释放,降低资源竞争概率。

死锁检测与处理的实操命令(Linux 系统)

  • 查看锁持有情况cat /proc/locks,列出所有进程持有的锁信息(包括进程 ID、锁类型、锁地址等)。
  • 查看 System V 信号量 / 共享内存资源ipcs -s(信号量)、ipcs -m(共享内存),查看系统中残留的资源。
  • 手动删除残留资源ipcrm -s 信号量ID(删除信号量)、ipcrm -m 共享内存ID(删除共享内存),用于清理死锁进程残留的资源。
  • 强制终止死锁进程kill -9 进程ID,通过终止死锁进程释放资源,需先通过pstop命令获取死锁进程的 ID。
相关推荐
小白跃升坊13 分钟前
基于1Panel的AI运维
linux·运维·人工智能·ai大模型·教学·ai agent
跃渊Yuey32 分钟前
【Linux】线程同步与互斥
linux·笔记
舰长11534 分钟前
linux 实现文件共享的实现方式比较
linux·服务器·网络
zmjjdank1ng1 小时前
Linux 输出重定向
linux·运维
路由侠内网穿透.1 小时前
本地部署智能家居集成解决方案 ESPHome 并实现外部访问( Linux 版本)
linux·运维·服务器·网络协议·智能家居
VekiSon1 小时前
Linux内核驱动——基础概念与开发环境搭建
linux·运维·服务器·c语言·arm开发
zl_dfq2 小时前
Linux 之 【进程信号】(signal、kill、raise、abort、alarm、Core Dump核心转储机制)
linux
Ankie Wan2 小时前
cgroup(Control Group)是 Linux 内核提供的一种机制,用来“控制、限制、隔离、统计”进程对系统资源的使用。
linux·容器·cgroup·lxc
skywalk81632 小时前
尝试在openi启智社区的dcu环境安装ollama最新版0.15.2(失败)
linux·运维·服务器·ollama
zhengfei6113 小时前
AutoPentestX – Linux 自动化渗透测试和漏洞报告工具
linux·运维·自动化