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

线程挂起的核心原理

线程挂起(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)
酷番叔酷番叔
上一篇 12小时前
下一篇 11小时前

相关推荐

  • 为何设备即文件是Linux核心原理?

    在Unix/Linux系统中,所有硬件设备(如磁盘、键盘、打印机)都被抽象为特殊文件,统一存放在/dev目录下,用户和程序可以像操作普通文件一样(使用open、read、write、close等系统调用)访问和控制这些设备,通过文件系统接口屏蔽底层硬件差异。

    2025年7月21日
    1100
  • 如何查看Linux当前WiFi接口名称

    在Linux系统中断开WiFi连接是常见的网络管理需求,无论是为了节省电量、切换网络还是解决连接问题,以下是多种可靠方法,涵盖命令行和图形界面操作,适用于不同技术水平的用户,操作前请确保您有管理员权限(部分命令需sudo),命令行方法(推荐)命令行提供最直接的控制,适合所有Linux发行版(Ubuntu、Fed……

    2025年6月14日
    2300
  • 如何强制停止Linux的tail监控?

    前台运行的tail(最常见情况)当tail命令直接在终端中执行时(占用当前终端):操作:按下组合键 Ctrl + C原理:Ctrl + C 会发送 SIGINT 信号,强制终止当前前台进程,示例:$ tail -f /var/log/syslog # 执行后持续输出日志^C # 按下Ctrl+C,tail立即终……

    3天前
    400
  • 如何安全删除双系统里的Linux文件夹?

    重要警告本操作涉及磁盘分区和系统引导,操作不当可能导致数据丢失或系统无法启动,请务必提前备份重要数据,并确保具备基础计算机操作能力,建议初学者在专业人士指导下进行,核心操作原则不可直接删除文件夹Linux系统文件与Windows分属不同磁盘分区,直接删除/boot、/home等文件夹会破坏引导和分区结构,必须两……

    2天前
    500
  • 设备文件如何代表内核对象?

    设备文件是Linux/Unix系统中特殊的文件类型,它们作为用户空间程序访问硬件设备或内核功能的抽象接口,这些文件(如/dev/sda, /dev/ttyS0)并非存储数据,而是代表内核中的设备驱动程序对象,程序通过标准的文件操作(如open, read, write)与底层硬件或内核服务进行交互。

    2天前
    500

发表回复

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

联系我们

400-880-8834

在线咨询: QQ交谈

邮件:HI@E.KD.CN

关注微信