为何pthread_barrier_wait不考虑线程优先级?如何确保高优线程先执行?
问题分析与解决方案
这是POSIX线程的预期行为吗?
是的,这完全符合POSIX标准的预期行为。pthread_barrier_wait()的设计仅保证所有线程都到达同步点后再继续执行,但POSIX没有规定线程被唤醒后的执行顺序必须遵循优先级。具体来说,当屏障的计数达到阈值时,底层实现可以以任意顺序唤醒等待的线程——哪怕高优先级线程也可能晚于低优先级线程被调度执行。
为什么会出现这种情况?
你的代码中,虽然给两个线程设置了不同的SCHED_FIFO优先级且绑定到同一个核心,但屏障唤醒的逻辑并不感知线程优先级。当两个线程都调用pthread_barrier_wait()后,屏障的底层实现可能会按线程进入等待的顺序、或是内核调度的随机顺序来唤醒线程,这就导致低优先级的task_2偶尔会先被唤醒并执行start_task_2()。
如何确保task_1始终先于task_2启动?
你需要在屏障同步之后,再添加一个优先级感知的同步机制,强制高优先级线程先执行。下面提供两种可靠的实现方案:
方案一:使用优先级保护互斥锁(PTHREAD_PRIO_PROTECT)
这种方法利用SCHED_FIFO调度策略下高优先级线程会优先获取互斥锁的特性,确保task_1先执行:
- 初始化一个带有
PTHREAD_PRIO_PROTECT属性的互斥锁,将优先级天花板设置为task_1的最高优先级(MAX_PRIORITY)。 - 两个线程在
pthread_barrier_wait()之后,都尝试获取这个互斥锁。由于task_1优先级更高,它会先拿到锁并执行任务,释放锁后task_2才能继续。
修改后的关键代码如下:
#include <pthread.h> #include <sched.h> #include <unistd.h> #include <errno.h> #include <stdio.h> #include <sys/types.h> #define MAX_PRIORITY 99 #define MIN_PRIORITY 1 pthread_t threads[2]; pthread_barrier_t barrier; pthread_mutex_t priority_mutex; void set_priority(int priority, int t_id){ int policy = SCHED_FIFO; struct sched_param param; param.sched_priority = priority; pthread_attr_t attr; pthread_attr_init (&attr); pthread_setschedparam(pthread_self(), policy, ¶m); pthread_getschedparam(pthread_self(), &policy, ¶m); } int set_core(int core_id) { int num_cores = sysconf(_SC_NPROCESSORS_ONLN); if (core_id < 0 || core_id >= num_cores) return EINVAL; cpu_set_t cpuset; CPU_ZERO(&cpuset); CPU_SET(core_id, &cpuset); pthread_t current_thread = pthread_self(); return pthread_setaffinity_np(current_thread, sizeof(cpu_set_t), &cpuset); } void create_task(int task_number, void *task) { int rc = pthread_create(&threads[task_number - 1], NULL, task, NULL); if(rc != 0) { printf("pthread_create(%d) error %d\n", task_number - 1, rc); pthread_exit(0) ; } } void schedule_task(int task_number, int priority) { set_core(2); //running tasks only in 2nd processor core set_priority(priority, task_number); } void start_task_1() { printf("Task 1 Started \n"); sleep(1); //do task 1 printf("Task 1 Endeded\n"); } void start_task_2() { printf("Task 2 Started \n"); sleep(1); //do task 2 printf("Task 2 Endeded\n"); } void task_1(void *thread_param) { schedule_task(1, MAX_PRIORITY); pthread_barrier_wait(&barrier); pthread_mutex_lock(&priority_mutex); start_task_1(); pthread_mutex_unlock(&priority_mutex); pthread_exit(NULL); } void task_2(void *thread_param) { schedule_task(2, MIN_PRIORITY); pthread_barrier_wait(&barrier); pthread_mutex_lock(&priority_mutex); start_task_2(); pthread_mutex_unlock(&priority_mutex); pthread_exit(NULL); } int main() { pthread_barrier_init(&barrier, NULL, 2); // 初始化优先级保护互斥锁 pthread_mutexattr_t mutex_attr; pthread_mutexattr_init(&mutex_attr); pthread_mutexattr_setprotocol(&mutex_attr, PTHREAD_PRIO_PROTECT); pthread_mutexattr_setprioceiling(&mutex_attr, MAX_PRIORITY); pthread_mutex_init(&priority_mutex, &mutex_attr); pthread_mutexattr_destroy(&mutex_attr); create_task(1, task_1); create_task(2, task_2); for (int i = 0; i < 2; i++) { pthread_join(threads[i], NULL); } pthread_barrier_destroy(&barrier); pthread_mutex_destroy(&priority_mutex); return 0; }
方案二:使用条件变量让task_2等待task_1启动
这种方法更直接,让task_1在屏障同步后先启动,再主动通知task_2开始执行:
- 初始化一个互斥锁和条件变量。
- task_1在
pthread_barrier_wait()后先执行任务,然后发送条件信号;task_2在屏障同步后,等待信号再执行任务。
关键代码修改如下:
#include <pthread.h> #include <sched.h> #include <unistd.h> #include <errno.h> #include <stdio.h> #include <sys/types.h> #define MAX_PRIORITY 99 #define MIN_PRIORITY 1 pthread_t threads[2]; pthread_barrier_t barrier; pthread_mutex_t cv_mutex; pthread_cond_t start_cond; int task1_started = 0; // 保留原有set_priority、set_core、create_task、schedule_task函数... void start_task_1() { printf("Task 1 Started \n"); sleep(1); //do task 1 printf("Task 1 Endeded\n"); } void start_task_2() { printf("Task 2 Started \n"); sleep(1); //do task 2 printf("Task 2 Endeded\n"); } void task_1(void *thread_param) { schedule_task(1, MAX_PRIORITY); pthread_barrier_wait(&barrier); start_task_1(); // 通知task_2可以启动 pthread_mutex_lock(&cv_mutex); task1_started = 1; pthread_cond_signal(&start_cond); pthread_mutex_unlock(&cv_mutex); pthread_exit(NULL); } void task_2(void *thread_param) { schedule_task(2, MIN_PRIORITY); pthread_barrier_wait(&barrier); // 等待task_1启动的信号 pthread_mutex_lock(&cv_mutex); while (!task1_started) { pthread_cond_wait(&start_cond, &cv_mutex); } pthread_mutex_unlock(&cv_mutex); start_task_2(); pthread_exit(NULL); } int main() { pthread_barrier_init(&barrier, NULL, 2); pthread_mutex_init(&cv_mutex, NULL); pthread_cond_init(&start_cond, NULL); create_task(1, task_1); create_task(2, task_2); for (int i = 0; i < 2; i++) { pthread_join(threads[i], NULL); } pthread_barrier_destroy(&barrier); pthread_mutex_destroy(&cv_mutex); pthread_cond_destroy(&start_cond); return 0; }
额外优化建议
你的代码中,线程优先级是在线程内部通过pthread_setschedparam()设置的,这种方式存在竞态风险(线程可能在设置优先级前就开始执行)。更可靠的方式是在创建线程时,通过pthread_attr_t直接指定调度参数:
void create_task(int task_number, void *task, int priority) { pthread_attr_t attr; pthread_attr_init(&attr); // 设置调度策略为SCHED_FIFO pthread_attr_setschedpolicy(&attr, SCHED_FIFO); struct sched_param param; param.sched_priority = priority; pthread_attr_setschedparam(&attr, ¶m); // 强制使用指定的调度属性,不继承主线程属性 pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); int rc = pthread_create(&threads[task_number - 1], &attr, task, NULL); if(rc != 0) { printf("pthread_create(%d) error %d\n", task_number - 1, rc); pthread_exit(0); } pthread_attr_destroy(&attr); }
这样可以确保线程从创建之初就拥有指定的优先级,避免潜在的竞态问题。
内容的提问来源于stack exchange,提问作者Bruno Pessanha
相关产品推荐
相关产品推荐

