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

线程挂起的核心原理

线程挂起(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

相关推荐

  • 文件系统如何运作?关键部分解析

    在Linux操作系统中,文件系统是管理数据存储的核心机制,它决定了文件如何被命名、存储、检索和更新,理解Linux文件系统的存储原理,不仅能帮助用户高效管理数据,还能优化系统性能,以下是Linux文件系统存储机制的详细解析:Linux文件系统通过多层结构组织数据,关键组件包括:超级块(Superblock)位于……

    2025年6月19日
    13800
  • 如何正确退出Linux系统?

    当您结束Linux系统的使用后,正确的退出流程至关重要,不当操作可能导致数据丢失或系统损坏,根据使用场景不同,退出方式主要分为以下两种:退出终端/命令行界面基础退出命令输入 exit 后按回车或按快捷键 Ctrl + D (发送EOF信号)适用场景:SSH连接、本地终端、虚拟控制台多层级终端退出若使用过 su……

    2025年8月6日
    12300
  • linux如何连接局域网

    Linux 中,可通过配置网络接口 IP 地址、子网掩码、网关等参数

    2025年8月15日
    9100
  • Linux系统如何制作光盘镜像?

    在Linux系统中制作光盘镜像是一项常见操作,主要用于备份光盘内容、创建系统安装盘或分发数据,光盘镜像通常以ISO格式存储,包含了光盘的所有数据结构和文件信息,本文将详细介绍在Linux系统中制作光盘镜像的多种方法,包括命令行工具和图形界面工具,并附上注意事项和常见问题解答,光盘镜像的基础概念光盘镜像(如ISO……

    2025年10月2日
    8100
  • Linux如何查看网络端口状态及占用情况?

    在Linux系统中,网络端口管理是系统运维和开发中的核心任务之一,无论是排查服务是否正常监听、诊断端口冲突,还是进行安全审计,都需要掌握查看网络端口的方法,Linux提供了多种命令和工具来查看端口状态、关联进程及详细信息,下面将详细介绍这些工具的使用方法及适用场景,使用netstat命令查看端口netstat是……

    2025年8月22日
    11600

发表回复

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

联系我们

400-880-8834

在线咨询: QQ交谈

邮件:HI@E.KD.CN

关注微信