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

线程挂起的核心原理

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

  • 调度器介入:挂起时线程状态从TASK_RUNNING变为TASK_INTERRUPTIBLE或TASK_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

相关推荐

  • 你的日志监控真的发挥价值了吗?

    Linux日志是系统运行的”黑匣子”,记录内核事件、服务状态、用户行为及安全审计信息,有效的日志监控能实现:故障诊断:快速定位服务崩溃、硬件错误根源安全防御:实时检测暴力破解、异常登录、恶意进程性能优化:分析资源瓶颈(如/var/log/syslog中的OOM记录)合规审计:满足GDPR/HIPAA等法规的数据……

    2025年7月26日
    21100
  • Linux如何快速识别磁盘类型?

    使用 lsblk 命令(推荐)lsblk 是最直观的工具,可显示磁盘的物理类型和拓扑关系:lsblk -d -o NAME,MODEL,SIZE,TRAN,TYPE输出示例:NAME MODEL SIZE TRAN TYPEsda Samsung SSD 870 1TB sata disk # SATA固态硬盘……

    2025年8月8日
    44700
  • Linux中如何删除文件夹?命令使用与操作步骤详解

    在Linux系统中,删除文件夹是日常管理文件系统的常见操作,但不同场景下需使用不同的命令和方法,以确保操作安全且高效,Linux删除文件夹主要涉及rmdir、rm命令,以及结合find等工具的高级用法,需根据文件夹是否为空、是否需要递归删除等需求选择合适的方式,基础删除命令:rmdir与rmrmdir:仅删除空……

    2025年8月31日
    28200
  • 如何安全移除MySQL核心包?,移除MySQL核心包有何影响?,MySQL依赖移除会出问题吗?,怎样彻底卸载MySQL核心包?,移除MySQL核心包注意事项?

    在Linux系统中卸载MySQL需要谨慎操作,避免残留文件影响后续安装或系统运行,以下是详细步骤,适用于主流发行版(如Ubuntu/Debian和CentOS/RHEL),操作前请务必备份重要数据,卸载前准备备份数据库(防止数据丢失):mysqldump -u root -p –all-databases……

    2025年8月7日
    30500
  • Linux挂载硬盘如何避免数据丢失?

    准备工作备份数据重要提示:操作磁盘可能导致数据丢失,务必提前备份重要文件,识别硬盘设备使用命令查看所有磁盘: sudo fdisk -l新硬盘通常显示为 /dev/sdb、/dev/nvme0n1 等(根据接口类型不同命名不同),通过容量和分区信息确认目标硬盘(如:/dev/sdb1 表示第一分区),安装必要工……

    2025年7月17日
    29600

发表回复

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

联系我们

400-880-8834

在线咨询: QQ交谈

邮件:HI@E.KD.CN

关注微信