关于pthread条件变量理解与多线程同步启动的技术咨询
理清pthread同步逻辑,实现多调度策略线程的统一启动测试
我完全懂你在折腾pthread互斥锁、条件变量时的迷茫——这俩组合的逻辑确实容易绕,尤其是你想精准控制9个不同调度策略的线程同时启动来测效率的场景。先帮你把核心逻辑捋顺,再给你一套可运行的代码示例,一步步拆解问题。
先纠正你的核心逻辑误区
你提到“主函数启动后先调用pthread_lock(&mutex),再通过pthread_cond_wait(&cond, &mutex)确保线程在我发出指令前不会启动”——这个逻辑是反的!如果主线程先挂在pthread_cond_wait上,那谁来唤醒它?正确的姿势应该是让子线程先阻塞在条件变量上,主线程等所有子线程创建完毕后,统一发信号唤醒它们。
条件变量的正确用法是:
- 用一个共享条件标志(比如
volatile int start_flag = 0)来表示“是否允许启动” - 子线程启动后,先加锁,然后循环检查这个标志:如果没到启动时机,就调用
pthread_cond_wait挂起(挂起时会自动释放互斥锁,被唤醒时又会重新获取锁) - 主线程等所有子线程创建完成后,加锁、设置标志为1、调用
pthread_cond_broadcast唤醒所有等待的子线程,最后解锁
实现步骤与代码示例
1. 全局变量与初始化
首先定义共享的互斥锁、条件变量,以及启动标志:
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h> #include <sched.h> #include <sys/time.h> // 同步用的全局变量 pthread_mutex_t mutex; pthread_cond_t cond; volatile int start_flag = 0; // 记录每个线程的耗时 typedef struct { int thread_id; int policy; struct timeval start_time; struct timeval end_time; } ThreadData;
2. 线程函数逻辑
每个线程先阻塞等待启动信号,被唤醒后执行测试任务(这里用循环模拟耗时操作),记录时间:
void* thread_func(void* arg) { ThreadData* data = (ThreadData*)arg; // 加锁,准备等待启动信号 pthread_mutex_lock(&mutex); // 用while循环避免虚假唤醒!这是条件变量的关键细节 while (!start_flag) { pthread_cond_wait(&cond, &mutex); } // 被唤醒后,记录启动时间 gettimeofday(&data->start_time, NULL); // 解锁,因为后续任务不需要持有锁(锁只用来同步启动时机) pthread_mutex_unlock(&mutex); // 模拟耗时任务:比如循环1e8次 long long i; for (i = 0; i < 100000000; i++); // 记录结束时间 gettimeofday(&data->end_time, NULL); return NULL; }
3. 主线程逻辑:创建线程、设置调度策略、统一启动
主线程负责初始化同步变量、创建9个不同调度策略的线程,最后唤醒它们并统计结果:
int main() { int i; pthread_t threads[9]; ThreadData thread_data[9]; // 三种调度策略,每个策略3个线程 int policies[] = {SCHED_FIFO, SCHED_RR, SCHED_OTHER, SCHED_FIFO, SCHED_RR, SCHED_OTHER, SCHED_FIFO, SCHED_RR, SCHED_OTHER}; struct sched_param param; // 初始化互斥锁和条件变量 pthread_mutex_init(&mutex, NULL); pthread_cond_init(&cond, NULL); // 创建9个线程,设置不同的调度策略 for (i = 0; i < 9; i++) { thread_data[i].thread_id = i; thread_data[i].policy = policies[i]; // 设置线程调度参数:SCHED_FIFO和SCHED_RR需要设置优先级 param.sched_priority = (policies[i] == SCHED_OTHER) ? 0 : 10; // 创建线程前先设置属性 pthread_attr_t attr; pthread_attr_init(&attr); // 设置调度策略 pthread_attr_setschedpolicy(&attr, policies[i]); // 设置调度参数 pthread_attr_setschedparam(&attr, ¶m); // 允许线程继承调度属性 pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); // 创建线程 if (pthread_create(&threads[i], &attr, thread_func, &thread_data[i]) != 0) { perror("pthread_create failed"); exit(EXIT_FAILURE); } pthread_attr_destroy(&attr); } // 等所有线程都创建完毕,准备统一启动 printf("All threads created, ready to start...\n"); sleep(1); // 给线程足够时间进入等待状态 // 加锁、设置启动标志、唤醒所有线程 pthread_mutex_lock(&mutex); start_flag = 1; pthread_cond_broadcast(&cond); pthread_mutex_unlock(&mutex); // 等待所有线程完成 for (i = 0; i < 9; i++) { pthread_join(threads[i], NULL); } // 统计并打印结果 printf("\nThread performance results:\n"); for (i = 0; i < 9; i++) { long long elapsed = (thread_data[i].end_time.tv_sec - thread_data[i].start_time.tv_sec) * 100000LL + (thread_data[i].end_time.tv_usec - thread_data[i].start_time.tv_usec); char* policy_name; switch (thread_data[i].policy) { case SCHED_FIFO: policy_name = "SCHED_FIFO"; break; case SCHED_RR: policy_name = "SCHED_RR"; break; case SCHED_OTHER: policy_name = "SCHED_OTHER"; break; default: policy_name = "UNKNOWN"; } printf("Thread %d (%s): %lld us\n", thread_data[i].thread_id, policy_name, elapsed); } // 清理资源 pthread_mutex_destroy(&mutex); pthread_cond_destroy(&cond); return 0; }
关键细节提醒
- 虚假唤醒:一定要用
while (!start_flag)而不是if,因为线程可能会被意外唤醒(比如系统信号),这时候需要重新检查条件 - 调度策略权限:
SCHED_FIFO和SCHED_RR需要以root权限运行程序,否则会设置失败 - 互斥锁的释放时机:子线程被唤醒后要立即解锁,避免长时间持有锁影响其他线程
- 线程属性的设置:必须用
pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED),否则线程会继承主线程的调度策略,设置无效
测试效率的注意点
- 可以多次运行取平均值,避免单次运行的偶然性
- 可以调整模拟任务的复杂度,让耗时更明显,方便对比
- 可以观察CPU使用率、线程切换次数等系统指标,辅助判断调度策略的效率
内容的提问来源于stack exchange,提问作者Lazarus
相关产品推荐
相关产品推荐

