C语言schedule是什么?如何实现?

99ANYc3cd6
预计阅读时长 22 分钟
位置: 首页 C语言 正文

"Schedule" 在 C 语言中没有一个像 printfmalloc 那样标准、内置的关键字或函数,它的含义完全取决于你正在讨论的上下文,它通常与操作系统多线程任务管理相关。

c语言schedule
(图片来源网络,侵删)

下面我将从最常见的几个方面来解释 "Schedule" 在 C 语言中的体现和应用。


操作系统层面的调度 (最核心的含义)

这是 "Schedule" 最经典和最底层的含义,操作系统内核的核心任务之一就是进程调度线程调度,它决定哪个进程或线程应该获得 CPU 的使用权,以及何时执行、执行多久。

核心概念:

  • 调度器: 操作系统内核的一部分,负责执行调度算法。
  • 调度算法: 决定调度策略的规则,
    • 先来先服务: 按照进程到达的先后顺序进行调度。
    • 短作业优先: 优先执行预计运行时间短的作业。
    • 时间片轮转: 为每个进程分配一个固定的时间片,用完则让出 CPU,排到队尾。
    • 多级反馈队列: 结合时间片和优先级,将进程分为不同优先级的队列。

C 语言如何与操作系统调度交互?

c语言schedule
(图片来源网络,侵删)

作为应用程序开发者,你通常不直接编写操作系统的调度器,你可以通过调用系统 API 来请求或影响调度行为,这些 API 通常用 C 语言来定义(尤其是在 Linux/Unix 系统中)。

关键的系统调用/函数:

a) fork(), exec(), wait(): 创建和管理进程

  • fork(): 创建一个当前进程的副本(子进程),操作系统调度器会决定父进程和子进程谁先运行。
  • exec(): 用一个新的程序替换当前进程的映像。
  • wait(): 父进程等待子进程结束。

示例代码 (Linux/Unix):

#include <stdio.h>
#include <unistd.h> // for fork, getpid
#include <sys/wait.h> // for wait
int main() {
    pid_t pid = fork(); // 创建子进程
    if (pid == 0) {
        // --- 子进程代码块 ---
        printf("Child process (PID: %d) is running.\n", getpid());
        sleep(2); // 模拟子进程执行耗时任务
        printf("Child process (PID: %d) is finished.\n", getpid());
    } else if (pid > 0) {
        // --- 父进程代码块 ---
        printf("Parent process (PID: %d) created child (PID: %d).\n", getpid(), pid);
        // 父进程可以选择等待子进程结束
        // wait(NULL); 
        // 如果没有 wait,父进程可能会先于子进程结束。
        // 操作系统调度器会独立调度这两个进程。
        printf("Parent process (PID: %d) is finished.\n", getpid());
    } else {
        // fork 失败
        perror("fork failed");
        return 1;
    }
    return 0;
}

fork() 之后,操作系统调度器接管了父进程和子进程的命运。 你无法精确控制谁先运行,这完全取决于内核的调度策略和当时的系统状态。

c语言schedule
(图片来源网络,侵删)

b) nice(): 修改进程优先级

nice() 函数可以调整进程的“优先级”,但这是一种“建议”而非命令,数值越高,表示越“nice”,优先级越低,越不容易被调度器选中。

示例代码 (Linux/Unix):

#include <stdio.h>
#include <unistd.h> // for nice
int main() {
    printf("Current process (PID: %d) is running with default priority.\n", getpid());
    // 尝试将进程优先级降低(变得更“nice”)
    // nice 值范围通常为 -20 (最高优先级) 到 19 (最低优先级)
    int ret = nice(10); // 增加 nice 值
    if (ret == -1) {
        perror("nice failed");
    } else {
        printf("Process priority changed. New nice value: %d\n", ret);
    }
    while(1); // 让进程一直运行,方便观察
    return 0;
}

c) sched_yield(): 主动让出 CPU

当你想让出当前 CPU 时间给其他就绪的进程或线程时,可以调用 sched_yield(),这是一种协作式调度的体现。

示例代码 (POSIX):

#include <stdio.h>
#include <sched.h> // for sched_yield
int main() {
    for (int i = 0; i < 5; i++) {
        printf("Process %d is doing some work...\n", i);
        sched_yield(); // 做完一点事后,主动让出CPU
    }
    return 0;
}

用户态线程库的调度

除了操作系统内核的线程(内核线程),还有用户态线程,用户态线程的调度完全由用户空间的库(如 POSIX Threads, pthreads)自己管理,不依赖于内核。

pthreads 库中的调度概念:

pthreads 是 C 语言中创建和管理线程的标准库,虽然线程的最终执行权仍在操作系统调度器手中,但 pthreads 提供了一些机制来管理这些用户态线程对象。

关键函数:

  • pthread_create(): 创建一个新线程,线程处于 "就绪" 状态,等待操作系统调度器为其分配 CPU。
  • pthread_join(): 阻塞当前线程,直到目标线程执行完毕。
  • pthread_mutex_t, pthread_cond_t: 互斥锁和条件变量,它们是实现线程同步的关键,而同步机制是用户态调度器实现任务切换和协调的基础。

示例代码 (pthreads):

#include <stdio.h>
#include <pthread.h>
// 线程函数
void* thread_func(void* arg) {
    int thread_num = *(int*)arg;
    printf("Thread %d: Hello, World!\n", thread_num);
    return NULL;
}
int main() {
    pthread_t thread1, thread2;
    int num1 = 1, num2 = 2;
    // 创建线程1
    if (pthread_create(&thread1, NULL, thread_func, &num1) != 0) {
        perror("Failed to create thread 1");
        return 1;
    }
    // 创建线程2
    if (pthread_create(&thread2, NULL, thread_func, &num2) != 0) {
        perror("Failed to create thread 2");
        return 1;
    }
    // 等待两个线程都执行完毕
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);
    printf("Main thread: All threads have finished.\n");
    return 0;
}

在这个例子中,pthread_create 让两个线程进入了就绪队列,操作系统调度器会决定哪个线程先获得 CPU 并执行 thread_funcpthread_join 则让主线程进入“阻塞”状态,直到目标线程结束,这本身也是一种调度行为。


应用程序自定义的任务调度

在很多应用中,特别是游戏引擎、网络服务器或嵌入式系统中,开发者需要实现自己的任务调度系统,以实现更精细的控制,

  • 游戏循环: 以固定的频率更新游戏状态、渲染画面。
  • 任务队列: 将耗时的任务(如网络请求、文件 I/O)放入队列,由一个或多个工作线程异步处理。
  • 定时器: 在指定的时间点执行某个函数。

实现方式:

这种调度通常不是用单个函数,而是通过以下技术组合实现:

  1. 事件循环: 主线程不断循环,检查是否有事件发生(如定时器到期、任务到达),然后执行相应的处理函数。
  2. 状态机: 将复杂的任务分解成多个状态,通过调度器在不同状态之间切换。
  3. 回调函数: 将任务的执行逻辑封装成函数,在合适的时机被调用。

示例代码 (一个简单的任务调度器雏形):

#include <stdio.h>
#include <stdbool.h>
#include <unistd.h> // for sleep
// 定义一个任务函数类型
typedef void (*TaskFunc)();
// 定义一个任务结构体
typedef struct {
    TaskFunc func;
    char* name;
} Task;
// 任务列表
Task tasks[] = {
    { (TaskFunc)printf, "Task 1: Print a message." },
    { (TaskFunc)sleep, "Task 2: Sleep for 1 second." },
    { (TaskFunc)printf, "Task 3: Print another message." }
};
int num_tasks = sizeof(tasks) / sizeof(tasks[0]);
// 简单的调度器函数
void run_scheduler() {
    printf("Scheduler started.\n");
    for (int i = 0; i < num_tasks; i++) {
        printf("Executing %s\n", tasks[i].name);
        // 执行任务
        if (tasks[i].func == (TaskFunc)sleep) {
            // 特殊处理 sleep,因为它需要参数
            // 在真实场景中,你可能需要更复杂的机制
            sleep(1);
        } else {
            tasks[i].func(); // 直接调用函数
        }
        printf("Finished %s\n", tasks[i].name);
    }
    printf("Scheduler finished.\n");
}
int main() {
    run_scheduler();
    return 0;
}

这个例子非常简单,但它展示了应用程序层面调度的核心思想:按顺序或根据特定规则执行一系列预定义的任务,更高级的调度器会包含优先级、并发、任务依赖等复杂逻辑。


调度层面 核心概念 C 语言中的体现 关键函数/库
操作系统内核 进程/线程调度,决定谁使用 CPU 通过系统调用与内核交互,影响或观察调度行为 fork(), exec(), wait(), nice(), sched_yield()
用户态线程库 管理线程对象,实现线程同步 创建、同步、管理用户态线程 pthreads (pthread_create, pthread_join, pthread_mutex...)
应用程序自定义 实现特定业务逻辑的任务调度 设计任务队列、事件循环、状态机等模式 无标准函数,依赖开发者自定义逻辑和 struct

当你在 C 语言中谈论 "Schedule" 时,首先要明确你指的是哪个层面的调度,是请求操作系统调度,还是管理你自己的线程,或者是实现你自己的任务队列?不同的层面,其实现方式和关注点都完全不同。

-- 展开阅读全文 --
头像
Modules在C语言中如何实现?
« 上一篇 04-13
织梦如何添加百度编辑器?
下一篇 » 04-13

相关文章

取消
微信二维码
支付宝二维码

目录[+]