线程挂起的核心原理
线程挂起(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