线程挂起的核心原理
线程挂起(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);
关键注意事项与最佳实践
-
避免竞态条件
- 使用条件变量时,必须用循环检查条件(
while (!condition)
),防止虚假唤醒。 - 互斥锁保护共享数据,例如修改
ready
标志前加锁。
- 使用条件变量时,必须用循环检查条件(
-
选择合适方法
- 同步任务 → 条件变量/信号量
- I/O等待 →
poll
/epoll
- 定时任务 →
nanosleep
- 信号处理 →
sigwait
-
资源释放
- 确保挂起前释放锁(如
pthread_cond_wait
内部释放锁),避免死锁。
- 确保挂起前释放锁(如
-
唤醒丢失问题
信号量唤醒不会丢失,条件变量需在修改条件后立即唤醒。
-
线程安全函数
- 优先使用线程安全函数(如
nanosleep
而非sleep
)。
- 优先使用线程安全函数(如
方法 | 适用场景 | 优点 | 缺点 |
---|---|---|---|
条件变量 | 复杂条件同步 | 精准唤醒、节省CPU | 需搭配互斥锁 |
信号量 | 资源计数控制 | 唤醒不丢失、操作简单 | 不适用于复杂条件 |
I/O阻塞 | 文件/网络通信 | 内核自动调度、高效 | 仅限I/O场景 |
定时挂起 | 延迟任务 | 简单易用 | 无法被外部事件唤醒 |
信号等待 | 跨进程通信或中断处理 | 响应系统信号 | 信号处理复杂度高 |
重要提示:
- 生产环境中优先使用条件变量或信号量,它们是线程同步的标准工具。
- 避免使用已废弃的
pause()
或不可靠的sleep()
。- 死锁调试工具:
gdb
结合pstack
分析线程堆栈。
引用说明:
- Linux Programmer’s Manual:
pthread_cond_wait(3)
,sem_wait(3)
- POSIX.1-2017 Standard (IEEE Std 1003.1)
- 《Unix环境高级编程》(Advanced Programming in the UNIX Environment)
- Linux内核源码文档:
Documentation/scheduler/sched-design-CFS.txt
原创文章,发布者:酷番叔,转转请注明出处:https://cloud.kd.cn/ask/9219.html