为什么你的手机拍不出好照片?

线程挂起的核心原理

线程挂起(Blocking)指线程主动让出CPU并进入休眠状态,直到被特定事件唤醒,这依赖于内核的调度机制:

  • 调度器介入:挂起时线程状态从TASK_RUNNING变为TASK_INTERRUPTIBLETASK_UNINTERRUPTIBLE
  • 唤醒机制:通过信号、条件变量或I/O事件等触发重新调度。

五种主动挂起线程的方法

条件变量(pthread_cond_wait)

原理:与互斥锁配合,当条件不满足时释放锁并挂起线程。

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
bool ready = false;
// 等待线程
void* thread_wait(void* arg) {
    pthread_mutex_lock(&mutex);
    while (!ready) {  // 必须用循环检查条件(避免虚假唤醒)
        pthread_cond_wait(&cond, &mutex); // 挂起并释放锁
    }
    pthread_mutex_unlock(&mutex);
    return NULL;
}
// 唤醒线程
void signal_thread() {
    pthread_mutex_lock(&mutex);
    ready = true;
    pthread_cond_signal(&cond);  // 唤醒一个等待线程
    pthread_mutex_unlock(&mutex);
}

信号量(sem_wait)

原理:通过计数器控制资源访问,值为0时挂起线程。

#include <semaphore.h>
sem_t sem;
// 初始化信号量(初始值为0)
sem_init(&sem, 0, 0);
// 等待线程
void* blocking_thread() {
    sem_wait(&sem);  // 若sem>0则减1,否则挂起
    printf("Thread resumed!\n");
    return NULL;
}
// 唤醒线程
void wake_thread() {
    sem_post(&sem);  // 信号量值+1,唤醒等待线程
}

文件描述符阻塞(read / poll)

原理:I/O操作在无数据时自动挂起线程。

#include <unistd.h>
int pipe_fd[2];
pipe(pipe_fd);  // 创建管道
// 读线程(无数据时挂起)
void* read_thread() {
    char buf[32];
    read(pipe_fd[0], buf, sizeof(buf)); // 阻塞直到管道有数据
    return NULL;
}
// 写线程唤醒
write(pipe_fd[1], "data", 5);  // 写入数据唤醒读线程

定时挂起(nanosleep)

原理:通过系统调用指定休眠时间。

#include <time.h>
void sleep_thread() {
    struct timespec delay = {.tv_sec = 2, .tv_nsec = 0}; // 休眠2秒
    nanosleep(&delay, NULL);  // 线程主动挂起指定时间
}

信号挂起(pause / sigwait)

原理:等待特定信号唤醒线程。

#include <signal.h>
void wait_for_signal() {
    sigset_t set;
    sigemptyset(&set);
    sigaddset(&set, SIGUSR1);
    int sig;
    sigwait(&set, &sig);  // 挂起直到收到SIGUSR1
}
// 其他线程发送信号唤醒
pthread_kill(target_thread_id, SIGUSR1);

关键注意事项与最佳实践

  1. 避免竞态条件

    • 使用条件变量时,必须用循环检查条件while (!condition)),防止虚假唤醒。
    • 互斥锁保护共享数据,例如修改ready标志前加锁。
  2. 选择合适方法

    • 同步任务 → 条件变量/信号量
    • I/O等待 → poll/epoll
    • 定时任务 → nanosleep
    • 信号处理 → sigwait
  3. 资源释放

    • 确保挂起前释放锁(如pthread_cond_wait内部释放锁),避免死锁。
  4. 唤醒丢失问题

    信号量唤醒不会丢失,条件变量需在修改条件后立即唤醒。

  5. 线程安全函数

    • 优先使用线程安全函数(如nanosleep而非sleep)。

方法 适用场景 优点 缺点
条件变量 复杂条件同步 精准唤醒、节省CPU 需搭配互斥锁
信号量 资源计数控制 唤醒不丢失、操作简单 不适用于复杂条件
I/O阻塞 文件/网络通信 内核自动调度、高效 仅限I/O场景
定时挂起 延迟任务 简单易用 无法被外部事件唤醒
信号等待 跨进程通信或中断处理 响应系统信号 信号处理复杂度高

重要提示

  • 生产环境中优先使用条件变量或信号量,它们是线程同步的标准工具。
  • 避免使用已废弃的pause()或不可靠的sleep()
  • 死锁调试工具:gdb结合pstack分析线程堆栈。

引用说明

  1. Linux Programmer’s Manual: pthread_cond_wait(3), sem_wait(3)
  2. POSIX.1-2017 Standard (IEEE Std 1003.1)
  3. 《Unix环境高级编程》(Advanced Programming in the UNIX Environment)
  4. Linux内核源码文档:Documentation/scheduler/sched-design-CFS.txt

原创文章,发布者:酷番叔,转转请注明出处:https://cloud.kd.cn/ask/9219.html

(0)
酷番叔酷番叔
上一篇 2025年7月29日 18:16
下一篇 2025年7月29日 18:40

相关推荐

  • ip命令为何更受推荐?

    在Linux系统中,网卡(网络接口卡)是连接计算机与网络的关键硬件,无论是排查网络问题、配置IP地址还是优化性能,准确查看网卡信息都是必备技能,以下是几种专业、可靠且高效的查看方法,适用于所有主流Linux发行版(如Ubuntu、CentOS、Debian等):ip 是现代Linux网络配置的标准工具,取代了旧……

    2025年7月28日
    6500
  • U盘如何安装Linux系统?新手详细步骤教程指南

    将Linux系统安装到U盘是一种灵活便携的方式,既能作为随身系统使用,也能用于体验Linux或系统修复,以下是详细的操作步骤,从准备工作到安装完成的全流程,帮助你顺利完成操作,U盘安装Linux系统准备工作在开始前,需确保硬件和软件准备到位,避免中途出现问题,以下是关键准备工作清单:项目具体要求备注U盘容量建议……

    2025年8月22日
    6100
  • 如何重装Linux软件?

    当Linux系统出现软件损坏、配置混乱或需要彻底重置时,可通过以下两种方式重做系统软件,根据需求选择完全重装系统或针对性修复软件包,操作前务必备份重要数据,完全重装Linux系统(彻底重置)适用于系统崩溃或需要全新环境的情况,准备工作备份数据:将 /home、/etc 等目录的重要文件复制到外部存储下载系统镜像……

    2025年7月1日
    8800
  • Linux系统如何正确注销?

    在Linux操作系统中,“注销”指的是结束当前用户的登录会话,返回到登录界面(如GDM、SDDM、LightDM等显示管理器的登录界面),以便其他用户登录或当前用户重新登录,与关机、重启不同,注销不会关闭系统,仅终止当前用户的进程和会话环境,确保用户数据安全退出,以下是Linux系统中注销的详细方法,涵盖图形界……

    2025年9月29日
    4900
  • linux杀掉之后如何启动

    Linux中,若误杀掉进程后想重新启动,需根据具体服务或应用,使用相应

    2025年8月10日
    5700

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

联系我们

400-880-8834

在线咨询: QQ交谈

邮件:HI@E.KD.CN

关注微信