单核心支持多少线程?4核多线程程序不同调度策略并发数咨询
咱们先把核心问题拆解清楚,再给你针对性的代码修改建议:
一、三种调度策略的并发运行上限
你对SCHED_FIFO的判断完全正确,咱们逐个说:
1. SCHED_FIFO(实时先来先服务)
作为实时调度策略,它的优先级高于所有非实时线程。在4核机器上,同一优先级的SCHED_FIFO线程最多同时运行4个——每个物理核心同一时间只能承载一个线程,同优先级的FIFO线程会按“先来先服务”的顺序占用核心,一旦拿到CPU就会一直运行,直到主动放弃(比如调用sched_yield())或者被更高优先级的实时线程抢占。如果你创建超过4个同优先级FIFO线程,剩下的会进入等待队列,等核心空闲后再执行。
2. SCHED_RR(实时时间片轮转)
同样是实时调度策略,优先级和SCHED_FIFO处于同一层级(都高于非实时)。它和FIFO的核心区别是:同优先级的RR线程会按固定时间片轮流占用CPU。但从“同时运行”的角度看,同一优先级的SCHED_RR线程最大并发数还是4个——毕竟物理核心只有4个,时间片轮转只是让同优先级线程交替使用核心,但同一时刻每个核心只能跑一个线程。如果创建8个同优先级RR线程,它们会分成4组交替运行,总耗时会接近单个线程的2倍(因为每个线程只能分到一半的CPU时间)。
3. SCHED_OTHER(普通CFS调度)
这是Linux默认的非实时调度策略,采用完全公平调度(CFS)算法。从“真正并发运行”的角度来说,它的上限也是4个——物理核心数决定了同一时刻能跑的线程数。不过CFS会通过时间片轮转让多个线程“看起来”同时运行,比如你创建8个SCHED_OTHER线程,系统会给每个线程分配CPU时间片,交替执行,但同一时刻还是只有4个线程在真正跑。另外要注意:只要有实时线程(FIFO/RR)在运行,SCHED_OTHER线程会被抢占,只有当所有实时线程都处于等待状态时,普通线程才能获得CPU时间。
二、针对测试需求的代码修改建议
你的代码是用来测试线程完成延迟任务的耗时,咱们可以调整代码来验证上面的结论,核心修改点如下:
1. 新增调度策略设置函数
实时调度策略需要特权(root权限)才能设置,所以要给每个线程指定调度策略和优先级:
#include <pthread.h> #include <sched.h> #include <stdio.h> #include <time.h> #include <unistd.h> #include <sys/resource.h> #include <string.h> // 线程数据结构,存ID、调度策略等信息 typedef struct { int id; int policy; int priority; // 实时策略用,SCHED_OTHER忽略 int nice; // SCHED_OTHER用 } ThreadData; // 设置线程调度策略 int set_thread_sched(pthread_t thread, int policy, int priority, int nice) { struct sched_param param; memset(¶m, 0, sizeof(param)); param.sched_priority = priority; // 设置实时调度策略 if (policy == SCHED_FIFO || policy == SCHED_RR) { if (pthread_setschedparam(thread, policy, ¶m) != 0) { perror("Failed to set real-time scheduler"); return -1; } } // 设置SCHED_OTHER的nice值 else if (policy == SCHED_OTHER) { if (setpriority(PRIO_PROCESS, 0, nice) != 0) { perror("Failed to set nice value"); return -1; } } return 0; }
2. 修改线程任务函数,记录核心ID和耗时
让每个线程记录自己运行的核心、开始/结束时间,方便你观察并发情况:
void *thread_task(void *arg) { ThreadData *data = (ThreadData *)arg; struct timespec start, end; clock_gettime(CLOCK_MONOTONIC, &start); // 执行足够长的耗时任务,避免被快速调度走 for (long i = 0; i < 1000000000; i++) { __asm__ __volatile__("nop"); // 空操作,防止编译器优化掉循环 } clock_gettime(CLOCK_MONOTONIC, &end); long long elapsed = (end.tv_sec - start.tv_sec) * 1000000000LL + (end.tv_nsec - start.tv_nsec); int cpu = sched_getcpu(); // 获取当前运行的核心ID const char *policy_name; switch(data->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 | Policy: %s | CPU: %d | Elapsed: %.2f ms\n", data->id, policy_name, cpu, elapsed / 1000000.0); pthread_exit(NULL); }
3. 主函数批量创建线程测试
你可以分批次测试:先单独测试FIFO,再测试RR,最后测试SCHED_OTHER,也可以混合测试抢占情况:
int main() { const int THREAD_COUNT = 8; // 测试超过核心数的线程 pthread_t threads[THREAD_COUNT]; ThreadData thread_data[THREAD_COUNT]; // 示例:创建8个SCHED_FIFO线程(优先级设为50) for (int i = 0; i < THREAD_COUNT; i++) { thread_data[i].id = i; thread_data[i].policy = SCHED_FIFO; thread_data[i].priority = 50; thread_data[i].nice = 0; if (pthread_create(&threads[i], NULL, thread_task, &thread_data[i]) != 0) { perror("Failed to create thread"); return 1; } // 设置调度策略 set_thread_sched(threads[i], SCHED_FIFO, 50, 0); } // 等待所有线程完成 for (int i = 0; i < THREAD_COUNT; i++) { pthread_join(threads[i], NULL); } return 0; }
4. 测试注意事项
- 必须用root权限运行程序,否则无法设置实时调度策略;
- 如果要测试SCHED_OTHER,把
policy改成SCHED_OTHER,调整nice值即可; - 观察输出:如果是FIFO/RR线程,前4个线程的耗时会接近(并行运行),后4个的耗时会是前4个的总和左右(等待核心空闲);SCHED_OTHER的8个线程耗时会接近单个线程的2倍(因为时间片轮转,每个线程分到一半CPU时间)。
内容的提问来源于stack exchange,提问作者Lazarus

