操作系统八股文整理(一)
操作系统八股文整理
- 一、进程和线程的区别
- 二、进程与线程的切换过程
- 一、进程切换
- 进程切换的步骤:
- 二、线程切换
- 线程切换的步骤:
- 三、进程切换与线程切换的对比
- 四、上下文切换的优化
- 三、系统调用
- 一、系统调用的触发
- 二、从用户空间切换到内核空间
- 三、执行内核函数
- 四、从内核空间返回用户空间
- 五、系统调用的完整流程示例
- 六、系统调用的性能开销
- 四、进程间通讯方式
- 管道demo
- 命名管道demo
- 1. 创建命名管道
- 2. 写入者进程
- 3. 读取者进程
- 编译与运行
- 输出示例
- 注意事项
- 信号量
- 1. 信号量的分类
- 2. 信号量的实现
- 2.1 系统V信号量
- 示例代码:
- 2.2 POSIX信号量
- 示例代码:
- 2.3 Boost信号量
- 示例代码:
- 2.4 C++20 `std::semaphore`
- 示例代码:
- 3. 信号量的使用场景
- 4. 注意事项
- 总结
- 消息队列demo
- 1. 系统V消息队列
- 示例代码:
- 2. POSIX消息队列
- 示例代码:
- 3. Boost.Interprocess消息队列
- 示例代码:
- 4. 自定义线程安全消息队列
- 示例代码:
- 总结
- 五、进程的调度
- 进程调度的算法
- (1)先来先服务(FCFS,First-Come-First-Served)
- (2)短作业优先(SJF,Shortest Job First)
- (3)优先级调度(Priority Scheduling)
- (4)时间片轮转(RR,Round Robin)
- (5)多级反馈队列(Multilevel Feedback Queue)
- 六、线程同步的方式
一、进程和线程的区别
二、进程与线程的切换过程
进程和线程的切换是操作系统中非常重要的概念,它们涉及到系统资源的分配、调度以及上下文切换等操作。以下是进程和线程切换过程的详细解释:
一、进程切换
进程切换是指操作系统将 CPU 从一个进程切换到另一个进程的过程。它通常发生在以下几种情况:
- 时间片用完:当前进程的时间片用完,操作系统需要选择下一个进程运行。
- 进程阻塞:当前进程因等待资源(如 I/O)而阻塞,操作系统需要切换到其他就绪的进程。
- 更高优先级进程到来:有更高优先级的进程就绪,操作系统需要切换到该进程。
进程切换的步骤:
- 保存当前进程的上下文:
- 保存程序计数器(PC):记录当前进程执行到的位置。
- 保存寄存器状态:包括通用寄存器、状态寄存器等,这些寄存器保存了进程执行时的临时数据和状态。
- 保存进程状态信息:如进程的优先级、资源使用情况等。
- 更新进程控制块(PCB):
- 将当前进程的状态从“运行态”改为“就绪态”或“阻塞态”,并更新其在进程队列中的位置。
- 选择下一个进程:
- 操作系统根据调度算法(如轮转调度、优先级调度等)选择下一个要运行的进程。
- 如果没有就绪的进程,可能会选择进入空闲进程或等待外部事件。
- 恢复新进程的上下文:
- 恢复程序计数器(PC):将新进程上次运行时的位置加载到 PC 中。
- 恢复寄存器状态:将新进程的寄存器状态加载到 CPU 的寄存器中。
- 更新新进程的状态:将新进程的状态从“就绪态”改为“运行态”。
- 开始新进程运行:
- 将 CPU 的控制权交给新进程,新进程从上次保存的位置继续执行。
二、线程切换
线程切换是指操作系统将 CPU 从一个线程切换到另一个线程的过程。线程切换的开销通常比进程切换小,因为线程共享进程的资源,切换时不需要切换进程的上下文。
线程切换的步骤:
- 保存当前线程的上下文:
- 保存线程的程序计数器(PC):记录当前线程执行的位置。
- 保存线程的寄存器状态:线程有自己的线程控制块(TCB),保存线程的局部变量、栈指针等信息。
- 保存线程状态信息:如线程的优先级、阻塞原因等。
- 更新线程控制块(TCB):
- 将当前线程的状态从“运行态”改为“就绪态”或“阻塞态”,并更新其在线程队列中的位置。
- 选择下一个线程:
- 操作系统根据线程调度算法(如时间片轮转、优先级调度等)选择下一个要运行的线程。
- 如果没有就绪的线程,可能会选择进入空闲线程或等待外部事件。
- 恢复新线程的上下文:
- 恢复线程的程序计数器(PC):将新线程上次运行时的位置加载到 PC 中。
- 恢复线程的寄存器状态:将新线程的寄存器状态加载到 CPU 的寄存器中。
- 更新新线程的状态:将新线程的状态从“就绪态”改为“运行态”。
- 开始新线程运行:
- 将 CPU 的控制权交给新线程,新线程从上次保存的位置继续执行。
三、进程切换与线程切换的对比
- 上下文切换的开销:
- 进程切换:开销较大,需要切换进程的代码段、数据段、寄存器状态、文件描述符等。
- 线程切换:开销较小,只需要切换线程的寄存器状态和栈指针等局部信息。
- 资源共享:
- 进程切换:进程之间是独立的,切换时需要切换资源。
- 线程切换:线程共享进程的资源,切换时不需要切换资源。
- 调度单位:
- 进程切换:以进程为单位进行调度。
- 线程切换:以线程为单位进行调度,线程是 CPU 调度的最小单位。
- 适用场景:
- 进程切换:适用于需要独立资源的场景,如运行不同的程序。
- 线程切换:适用于需要高并发的场景,如多线程服务器。
四、上下文切换的优化
上下文切换虽然可以提高系统的并发性,但频繁的上下文切换会增加系统的开销,降低系统性能。因此,操作系统和应用程序需要采取一些优化措施:
- 减少上下文切换的次数:
- 增加时间片的长度,减少进程或线程切换的频率。
- 使用高效的调度算法,减少不必要的上下文切换。
- 优化上下文切换的开销:
- 减少保存和恢复的寄存器数量。
- 使用缓存技术,减少对内存的访问。
- 减少线程的阻塞时间:
- 提高 I/O 操作的效率,减少线程因 I/O 阻塞而切换的次数。
- 使用非阻塞 I/O 或异步 I/O,减少线程的阻塞时间。
总之,进程切换和线程切换是操作系统中非常重要的机制,它们的合理使用可以提高系统的并发性和性能。
三、系统调用
系统调用是用户程序与操作系统内核之间进行交互的桥梁。它允许用户程序请求操作系统提供的服务,例如文件操作、进程控制、通信等。系统调用的整个流程涉及用户空间和内核空间的切换,以及参数传递和结果返回。以下是系统调用的详细流程:
一、系统调用的触发
用户程序需要操作系统服务时,会通过系统调用来请求。这通常通过以下方式触发:
- 使用特定的指令:
- 在 x86 架构中,通常使用
int 0x80
(中断指令)或syscall
指令来触发系统调用。 - 在 ARM 架构中,使用
svc
指令。 - 这些指令会中断用户程序的执行,将控制权转移到操作系统内核。
- 在 x86 架构中,通常使用
- 设置系统调用号和参数:
- 在触发系统调用之前,用户程序需要将系统调用号(标识要调用的服务)和参数(如文件名、缓冲区地址等)放置在特定的寄存器或栈中。
- 例如,在 x86 架构中,系统调用号通常放在
eax
寄存器中,参数放在ebx
、ecx
、edx
等寄存器中。
二、从用户空间切换到内核空间
当用户程序触发系统调用时,CPU 会从用户态切换到内核态,同时操作系统会进行以下操作:
- 保存用户态上下文:
- 操作系统会保存用户程序的上下文,包括寄存器状态(如程序计数器、栈指针等)和 CPU 的当前状态(用户态或内核态)。
- 这些信息通常保存在内核为每个进程分配的内核栈中。
- 切换到内核态:
- CPU 的特权级别从用户态(较低特权级别)切换到内核态(较高特权级别)。
- 内核态允许访问系统的全部资源,包括硬件设备和内核内存。
- 查找系统调用表:
- 操作系统根据系统调用号在系统调用表中查找对应的内核函数。
- 系统调用表是一个数组,每个系统调用号对应一个内核函数指针。
三、执行内核函数
找到对应的内核函数后,操作系统会执行以下操作:
- 参数传递:
- 内核函数会从寄存器或栈中获取用户程序传递的参数。
- 如果参数是用户空间的地址(如文件名字符串),内核需要进行地址检查,确保这些地址是合法的。
- 执行内核函数:
- 内核函数根据用户请求执行相应的操作,例如:
- 打开文件时,内核会查找文件系统,分配文件描述符。
- 写文件时,内核会将数据写入磁盘缓冲区。
- 创建进程时,内核会分配内存和资源,创建新的进程控制块(PCB)。
- 内核函数根据用户请求执行相应的操作,例如:
- 处理错误和异常:
- 如果操作失败(如文件不存在、权限不足),内核会设置错误码(如
errno
)。
- 如果操作失败(如文件不存在、权限不足),内核会设置错误码(如
四、从内核空间返回用户空间
内核函数执行完毕后,操作系统需要将控制权返回给用户程序:
- 保存内核态上下文:
- 操作系统保存内核态的上下文信息,包括内核函数的返回值(通常放在某个寄存器中)。
- 恢复用户态上下文:
- 操作系统从内核栈中恢复用户程序的上下文,包括寄存器状态和程序计数器。
- 这样用户程序可以从上次中断的地方继续执行。
- 切换回用户态:
- CPU 的特权级别从内核态切换回用户态。
- 返回结果:
- 内核将系统调用的结果(如文件描述符、返回值等)传递给用户程序。
- 如果发生错误,用户程序可以通过错误码(如
errno
)获取错误信息。
五、系统调用的完整流程示例
假设用户程序要调用 write()
系统调用来写文件,其流程如下:
- 用户程序准备参数:
- 将系统调用号(如
1
表示write
)放入eax
寄存器。 - 将文件描述符、缓冲区地址和写入字节数分别放入
ebx
、ecx
和edx
寄存器。
- 将系统调用号(如
- 触发系统调用:
- 用户程序执行
int 0x80
指令,触发中断。
- 用户程序执行
- 切换到内核态:
- 操作系统保存用户态上下文,切换到内核态。
- 根据系统调用号
1
,查找系统调用表,找到write()
的内核函数。
- 执行内核函数:
- 内核函数从寄存器中获取参数(文件描述符、缓冲区地址等)。
- 检查文件描述符是否有效,缓冲区地址是否合法。
- 将数据从用户空间的缓冲区复制到内核空间的缓冲区。
- 写入数据到磁盘缓冲区。
- 如果成功,返回写入的字节数;如果失败,设置错误码。
- 返回用户态:
- 操作系统保存内核态上下文,恢复用户态上下文。
- 切换回用户态,将返回值放入用户程序的寄存器中。
- 用户程序继续执行:
- 用户程序根据返回值判断写操作是否成功,并继续执行后续代码。
六、系统调用的性能开销
系统调用涉及用户空间和内核空间的切换,因此会产生一定的性能开销:
- 上下文切换开销:
- 保存和恢复寄存器状态、切换特权级别等操作会消耗时间。
- 参数传递和检查开销:
- 内核需要验证用户空间的地址是否合法,这可能涉及额外的内存访问。
- 内核函数执行开销:
- 内核函数的执行时间取决于系统调用的复杂性(如 I/O 操作可能涉及磁盘 I/O 延迟)。
- 系统调用的优化:
- 现代操作系统通过减少上下文切换的次数、使用更快的切换指令(如
syscall
)等方式来优化系统调用的性能。 - 一些系统调用(如
getpid()
)可以通过轻量级的机制(如vsyscall
或vdso
)直接在用户空间执行,避免切换到内核态。
- 现代操作系统通过减少上下文切换的次数、使用更快的切换指令(如
总之,系统调用是用户程序与操作系统交互的重要机制,其流程涉及用户空间和内核空间的切换、参数传递、内核函数执行以及结果返回。虽然系统调用会产生一定的开销,但它是实现操作系统功能的关键机制。
四、进程间通讯方式
管道demo
#include <iostream>
#include <unistd.h>
#include <cstring>
#include <cstdlib>int main() {int pipefd[2]; // 用于存储管道的文件描述符pid_t pid;// 创建管道if (pipe(pipefd) == -1) {exit(EXIT_FAILURE);}// 创建子进程pid = fork();if (pid == -1) {exit(EXIT_FAILURE);}if (pid > 0) { // 父进程// 关闭管道的写端close(pipefd[1]);// 从管道的读端读取数据char buffer[128];ssize_t bytes_read = read(pipefd[0], buffer, sizeof(buffer) - 1);if (bytes_read > 0) {buffer[bytes_read] = '\0'; // 确保字符串以 null 结尾std::cout << "Parent received: " << buffer << std::endl;} else {std::cerr << "Failed to read from pipe" << std::endl;}// 关闭管道的读端close(pipefd[0]);} else { // 子进程// 关闭管道的读端close(pipefd[0]);// 向管道的写端写入数据const char *msg = "Hello from child process!";ssize_t bytes_written = write(pipefd[1], msg, strlen(msg));if (bytes_written < 0) {std::cerr << "Failed to write to pipe" << std::endl;}// 关闭管道的写端close(pipefd[1]);}return 0;
}
命名管道demo
1. 创建命名管道
我们首先需要创建一个命名管道。这可以通过命令行工具 mkfifo
或在程序中使用 mkfifo()
系统调用来完成。
创建管道的命令行方式:
mkfifo /tmp/myfifo
创建管道的程序方式:
#include <sys/types.h>
#include <sys/stat.h>
#include <iostream>int main() {// 创建命名管道if (mkfifo("/tmp/myfifo", 0666) == -1) {perror("mkfifo");return -1;}std::cout << "Named pipe created at /tmp/myfifo" << std::endl;return 0;
}
2. 写入者进程
写入者进程向命名管道中写入数据。
writer.cpp:
#include <iostream>
#include <fcntl.h>
#include <unistd.h>
#include <cstring>int main() {const char *fifo_path = "/tmp/myfifo";const char *message = "Hello from writer process!\n";// 打开管道文件(写模式)int fd = open(fifo_path, O_WRONLY);if (fd == -1) {perror("open");return -1;}// 向管道写入数据if (write(fd, message, strlen(message)) == -1) {perror("write");close(fd);return -1;}std::cout << "Message sent: " << message << std::endl;// 关闭管道文件描述符close(fd);return 0;
}
3. 读取者进程
读取者进程从命名管道中读取数据。
reader.cpp
#include <iostream>
#include <fcntl.h>
#include <unistd.h>
#include <cstring>int main() {const char *fifo_path = "/tmp/myfifo";char buffer[128];// 打开管道文件(读模式)int fd = open(fifo_path, O_RDONLY);if (fd == -1) {perror("open");return -1;}// 从管道读取数据ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);if (bytes_read == -1) {perror("read");close(fd);return -1;}buffer[bytes_read] = '\0'; // 确保字符串以 null 结尾std::cout << "Message received: " << buffer << std::endl;// 关闭管道文件描述符close(fd);return 0;
}
编译与运行
- 创建管道:
- 如果没有提前创建管道,运行
writer
时会阻塞,直到管道被创建。 - 可以手动创建管道:bash复制
- 如果没有提前创建管道,运行
mkfifo /tmp/myfifo
- <font style="color:rgb(6, 6, 7);">或者运行创建管道的程序。</font>
- 编译代码:
- 将
writer.cpp
和reader.cpp
分别保存为文件。 - 使用以下命令编译:bash复制
- 将
g++ -o writer writer.cpp
g++ -o reader reader.cpp
- 运行程序:
- 在一个终端运行读取者程序:
./reader
- <font style="color:rgb(6, 6, 7);">在另一个终端运行写入者程序:</font>
./writer
输出示例
- reader终端:
Message received: Hello from writer process!
- writer终端:
Message sent: Hello from writer process!
注意事项
- 阻塞特性:
- 默认情况下,读取者会阻塞,直到有数据可读。
- 写入者会阻塞,直到有读取者打开管道。
- 如果不希望阻塞,可以在
open()
时使用O_NONBLOCK
标志。
- 管道文件路径:
- 确保管道文件路径一致(这里是
/tmp/myfifo
)。 - 如果路径不存在,写入者会阻塞,直到管道被创建。
- 确保管道文件路径一致(这里是
- 清理:
- 使用完管道后,可以手动删除管道文件:bash复制
rm /tmp/myfifo
- 跨进程通信:
命名管道允许不相关的进程之间通信,因此读取者和写入者可以是完全独立的程序。
通过这个示例,你可以看到命名管道如何实现进程间通信,非常适合需要跨进程传递数据的场景。
信号量
信号量(Semaphore)是一种用于进程间或线程间同步的机制,用于控制对共享资源的访问。它通过维护一个计数器来实现同步,当计数器大于零时,表示资源可用;当计数器为零时,表示资源已被占用。信号量通常用于解决互斥(Mutex)和同步(Sync)问题。
以下是信号量的基本操作:
- P操作(Wait/Down/Decrease):将信号量的值减1。如果减1后信号量的值小于0,则调用进程或线程阻塞,等待信号量的值变为非负。
- V操作(Signal/Up/Increase):将信号量的值加1。如果加1后信号量的值大于0,则唤醒一个等待该信号量的进程或线程。
1. 信号量的分类
信号量主要分为两种:
- 二进制信号量(Binary Semaphore):值只能为0或1,用于互斥访问。
- 计数信号量(Counting Semaphore):值可以是任意非负整数,用于控制多个资源的访问。
2. 信号量的实现
在C++中,信号量可以通过多种方式实现,包括系统V IPC信号量、POSIX信号量、Boost库以及C++20标准中的std::semaphore
。
2.1 系统V信号量
系统V信号量是基于IPC机制的信号量实现,使用semget
、semop
等函数。
示例代码:
cpp复制
#include <iostream>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.h>// P操作
void P(int semid) {struct sembuf sb;sb.sem_num = 0;sb.sem_op = -1; // 等待信号量sb.sem_flg = 0;semop(semid, &sb, 1);
}// V操作
void V(int semid) {struct sembuf sb;sb.sem_num = 0;sb.sem_op = 1; // 释放信号量sb.sem_flg = 0;semop(semid, &sb, 1);
}int main() {key_t key = ftok("semfile", 65); // 创建IPC keyint semid = semget(key, 1, 0666 | IPC_CREAT); // 创建信号量集// 初始化信号量union semun {int val;struct semid_ds* buf;unsigned short* array;} arg;arg.val = 1; // 初始值为1semctl(semid, 0, SETVAL, arg);// 模拟生产者和消费者if (fork() == 0) {// 子进程:消费者sleep(1); // 等待生产者P(semid); // 等待信号量std::cout << "Consumer: Consumed resource" << std::endl;V(semid); // 释放信号量} else {// 父进程:生产者P(semid); // 等待信号量std::cout << "Producer: Produced resource" << std::endl;V(semid); // 释放信号量}wait(nullptr); // 等待子进程结束semctl(semid, 0, IPC_RMID, arg); // 删除信号量集return 0;
}
2.2 POSIX信号量
POSIX信号量是另一种实现方式,使用sem_open
、sem_wait
和sem_post
等函数。
示例代码:
cpp复制
#include <iostream>
#include <semaphore.h>
#include <thread>
#include <unistd.h>int main() {sem_t sem;sem_init(&sem, 0, 1); // 初始化信号量,初始值为1// 生产者线程std::thread producer([&sem]() {sem_wait(&sem); // 等待信号量std::cout << "Producer: Produced resource" << std::endl;sem_post(&sem); // 释放信号量});// 消费者线程std::thread consumer([&sem]() {sleep(1); // 等待生产者sem_wait(&sem); // 等待信号量std::cout << "Consumer: Consumed resource" << std::endl;sem_post(&sem); // 释放信号量});producer.join();consumer.join();sem_destroy(&sem); // 销毁信号量return 0;
}
2.3 Boost信号量
Boost库提供了跨平台的信号量实现,使用boost::interprocess::named_semaphore
。
示例代码:
cpp复制
#include <boost/interprocess/sync/named_semaphore.hpp>
#include <iostream>
#include <thread>
#include <unistd.h>int main() {using namespace boost::interprocess;named_semaphore sem(open_or_create, "test_semaphore", 1); // 创建或打开信号量// 生产者线程std::thread producer([&sem]() {sem.wait();std::cout << "Producer: Produced resource" << std::endl;sem.post();});// 消费者线程std::thread consumer([&sem]() {sleep(1); // 等待生产者sem.wait();std::cout << "Consumer: Consumed resource" << std::endl;sem.post();});producer.join();consumer.join();named_semaphore::remove("test_semaphore"); // 删除信号量return 0;
}
2.4 C++20 std::semaphore
C++20标准引入了std::semaphore
,使得信号量的使用更加简洁。
示例代码:
cpp复制
#include <iostream>
#include <semaphore>
#include <thread>
#include <unistd.h>int main() {std::counting_semaphore<1> sem(1); // 创建信号量,初始值为1// 生产者线程std::thread producer([&sem]() {sem.acquire(); // 等待信号量std::cout << "Producer: Produced resource" << std::endl;sem.release(); // 释放信号量});// 消费者线程std::thread consumer([&sem]() {sleep(1); // 等待生产者sem.acquire(); // 等待信号量std::cout << "Consumer: Consumed resource" << std::endl;sem.release(); // 释放信号量});producer.join();consumer.join();return 0;
}
3. 信号量的使用场景
信号量主要用于以下场景:
- 互斥(Mutex):确保多个线程或进程不会同时访问共享资源。
- 同步(Sync):协调线程或进程的执行顺序,例如生产者-消费者问题。
4. 注意事项
- 死锁:如果信号量的使用不当,可能会导致死锁。例如,多个线程或进程同时等待同一个信号量。
- 资源泄漏:在使用系统V或POSIX信号量时,需要确保信号量在程序结束时被正确删除,否则可能会导致资源泄漏。
- 性能:信号量的使用会引入上下文切换的开销,因此需要合理设计同步机制。
总结
信号量是一种强大的同步机制,适用于多种并发场景。根据具体需求,可以选择系统V信号量、POSIX信号量、Boost信号量或C++20标准中的std::semaphore
。
消息队列demo
在C++中,消息队列是一种常见的进程间通信(IPC)机制,允许不同进程之间以异步方式交换消息。以下是关于C++消息队列的实现和使用方法的总结:
1. 系统V消息队列
系统V消息队列是一种传统的IPC机制,基于msgget
、msgsnd
和msgrcv
等函数。
示例代码:
cpp复制
#include <sys/ipc.h>
#include <sys/msg.h>
#include <iostream>
#include <cstring>struct message {long msg_type;char msg_text[100];
};int main() {key_t key = ftok("progfile", 65); // 创建IPC keyint msgid = msgget(key, 0666 | IPC_CREAT); // 创建消息队列message msg;msg.msg_type = 1;strcpy(msg.msg_text, "Hello World!");msgsnd(msgid, &msg, sizeof(msg), 0); // 发送消息std::cout << "Message sent: " << msg.msg_text << std::endl;// 接收消息msgrcv(msgid, &msg, sizeof(msg), 1, 0);std::cout << "Message received: " << msg.msg_text << std::endl;msgctl(msgid, IPC_RMID, nullptr); // 删除消息队列return 0;
}
此代码展示了如何创建消息队列、发送和接收消息。
2. POSIX消息队列
POSIX消息队列提供了另一种实现方式,使用mq_open
、mq_send
和mq_receive
等函数。
示例代码:
cpp复制
#include <mqueue.h>
#include <iostream>
#include <cstring>int main() {mqd_t mq;struct mq_attr attr;attr.mq_flags = 0;attr.mq_maxmsg = 10;attr.mq_msgsize = 1024;attr.mq_curmsgs = 0;mq = mq_open("/my_mq", O_CREAT | O_WRONLY, 0644, &attr); // 创建消息队列std::string msg = "Hello POSIX!";mq_send(mq, msg.c_str(), msg.size(), 0); // 发送消息std::cout << "Message sent: " << msg << std::endl;mq_close(mq);mq_unlink("/my_mq"); // 删除消息队列return 0;
}
3. Boost.Interprocess消息队列
Boost库提供了跨平台的消息队列实现,基于共享内存。
示例代码:
cpp复制
#include <boost/interprocess/ipc/message_queue.hpp>
#include <iostream>int main() {using namespace boost::interprocess;// 发送端message_queue mq(open_or_create, "test_mq", 100, sizeof(int));int msg = 42;mq.send(&msg, sizeof(msg), 0);std::cout << "Message sent: " << msg << std::endl;// 接收端int rcv_msg;size_t msg_size;unsigned priority;mq.receive(&rcv_msg, sizeof(rcv_msg), msg_size, priority);std::cout << "Message received: " << rcv_msg << std::endl;message_queue::remove("test_mq"); // 删除消息队列return 0;
}
4. 自定义线程安全消息队列
如果需要在多线程环境中使用消息队列,可以结合std::queue
、std::mutex
和std::condition_variable
实现线程安全的消息队列。
示例代码:
cpp复制
#include <queue>
#include <mutex>
#include <condition_variable>
#include <thread>
#include <iostream>struct Message {int messageType;std::string payload;
};class MessageQueue {
public:void enqueue(const Message& message) {std::unique_lock<std::mutex> lock(mutex_);queue_.push(message);condition_.notify_one();}Message dequeue() {std::unique_lock<std::mutex> lock(mutex_);condition_.wait(lock, [this] { return !queue_.empty(); });Message message = queue_.front();queue_.pop();return message;}private:std::queue<Message> queue_;std::mutex mutex_;std::condition_variable condition_;
};void producer(MessageQueue& mq) {Message msg{1, "Hello"};mq.enqueue(msg);
}void consumer(MessageQueue& mq) {Message msg = mq.dequeue();std::cout << "Received: " << msg.payload << std::endl;
}int main() {MessageQueue mq;std::thread prod(producer, std::ref(mq));std::thread cons(consumer, std::ref(mq));prod.join();cons.join();return 0;
}
总结
- 系统V和POSIX消息队列适用于进程间通信,但需要处理IPC资源的创建和销毁。
- Boost.Interprocess提供了跨平台的实现,基于共享内存。
- 自定义线程安全消息队列适用于多线程环境。
根据具体需求选择合适的消息队列实现方式。
五、进程的调度
进程调度的算法
进程调度的核心是调度算法,不同的算法适用于不同的场景。常见的调度算法包括:
(1)先来先服务(FCFS,First-Come-First-Served)
- 原理:按照进程到达的顺序分配CPU。
- 优点:简单直观。
- 缺点:可能导致短作业等待时间过长(“短作业饥饿”问题)。
(2)短作业优先(SJF,Shortest Job First)
- 原理:优先调度预计运行时间最短的进程。
- 优点:可以有效减少平均等待时间。
- 缺点:可能导致长作业饥饿,且需要预估进程运行时间。
(3)优先级调度(Priority Scheduling)
- 原理:根据进程的优先级分配CPU,优先级高的进程优先运行。
- 优点:可以满足不同进程的紧急程度需求。
- 缺点:低优先级的进程可能会被饿死(“优先级倒置”问题)。
(4)时间片轮转(RR,Round Robin)
- 原理:将CPU时间分成固定长度的时间片(Time Quantum),每个就绪态进程轮流运行一个时间片。
- 优点:公平性好,响应速度快,适合交互式系统。
- 缺点:时间片大小的选择会影响系统性能。
(5)多级反馈队列(Multilevel Feedback Queue)
- 原理:将就绪队列分为多个优先级队列,每个队列采用不同的调度算法。进程在不同队列之间动态迁移。
- 优点:综合了多种调度算法的优点,兼顾公平性和效率。
- 缺点:实现复杂,需要合理设计队列之间的迁移策略。
六、线程同步的方式
相关文章:
操作系统八股文整理(一)
操作系统八股文整理 一、进程和线程的区别二、进程与线程的切换过程一、进程切换进程切换的步骤: 二、线程切换线程切换的步骤: 三、进程切换与线程切换的对比四、上下文切换的优化 三、系统调用一、系统调用的触发二、从用户空间切换到内核空间三、执行…...
ssh转发笔记
工作中又学到了,大脑转不过来 现有主机A,主机B,主机C A能访问B,B能访问C,A不能访问C C上80端口有个服务,现在A想访问这个服务,领导让用ssh转发,研究半天没找到理想的语句…...
[从零开始学SSM] Bean的配置
bean基础配置 bean别名配置 bean的作用范围配置 由运行结果可知,Spring创建的bean默认是单例的 那么如果我想创建非单例的bean怎么办,这时候就需要用到配置的方式完成了:在<bean>的属性中添加一个scope属性,该属性默认是si…...
CML(Current Mode Logic)电平详解
一、CML的定义与核心特性 CML(Current Mode Logic,电流模式逻辑) 是一种基于 电流驱动 的高速差分信号标准,专为 10Gbps以上超高速传输 设计。其核心原理是通过恒定的尾电流源切换电流路径,生成低摆幅差分信号&#x…...
【链表世界的深度探索:从基础到高阶的算法解读】—— LeetCode
文章目录 反转链表链表的中间结点合并两个有序链表相交链表两数相加两两交换链表中的节点重排链表合并K个升序链表K个一组翻转链表 反转链表 这道题目的意思很好理解,即将链表给反转即可 方法一: 利用双指针进行操作,定义两个变量 prev 以及…...
JMeter 性能测试
Jmeter 用户手册 名词解释: RPS:每秒请求数-每秒向服务器发送多少请求数(一个场景,系统面临多大的压力) TPS:每秒事务数-每秒能够处理多少请求/事务数性能评价标准(其中的一个核心指标&#x…...
LCR 159. 库存管理 III
这道题虽然简单,但是可以有多种解法,适合练习各种解法。 可以用基于快速排序思想的快速选择算法: class Solution { public:vector<int> inventoryManagement(vector<int>& stock, int cnt) {vector<int> res;int le…...
Spring IOC(五个类注解)
controller、service、Repository、Component 、Configurationpackage com.java.ioc;import com.java.ioc.Controller.HelloController; import com.java.ioc.rep.UserRepository; import com.java.ioc.service.UserService; import org.springframework.boot.SpringApplicatio…...
JavaScript 中的包装类型:概念、作用与使用场景
文章目录 引言1. 什么是包装类型?1.1 包装类型的定义1.2 包装类型的作用 2. 包装类型的使用2.1 自动装箱(Autoboxing)示例 2.2 手动创建包装对象示例 3. 包装类型的特性3.1 包装对象的生命周期示例 3.2 基本类型与包装对象的区别示例 4. 包装…...
Linux 如何上传本地文件以及下载文件到本地命令总结
如果你希望在 Shell 终端中将远程服务器上的文件下载到本地电脑,可以使用以下工具和命令: 1. rz / sz(用于 Xshell、MobaXterm 等终端) 如果你使用的是Xshell、SecureCRT、MobaXterm等支持 rz/sz 的终端,可以使用 rz …...
CentOS 上扩展 Swap 分区的大小
在 CentOS 上扩展 Swap 分区的大小可以通过以下几种方式实现: 方法 1:增加 Swap 文件(推荐) 如果你的 Swap 是基于文件的(而不是分区),你可以增加 Swap 文件的大小,而不需要修改磁盘…...
清晰易懂的Miniconda安装教程
小白也能看懂的 Miniconda 安装教程 Miniconda 是一个轻量级的 Python 环境管理工具,适合初学者快速搭建 Python 开发环境。本教程将手把手教你如何在 Windows 系统上安装 Miniconda,并配置基础环境,确保你能够顺利使用 Python 进行开发。即…...
算法016——最小覆盖子串
力扣——最小覆盖子串(点击跳转) 分析题目 我们先随便从一个位置开始,让 right 右移,直到找到符合题目的位置停下 之后,让 left 右移,此时会出现两种情况 仍然符合要求,right 不需要动不符合…...
DeepSeek-R1大模型微调技术深度解析:架构、方法与应用全解析
1. DeepSeek-R1大模型架构设计与技术特性 1.1 架构设计 DeepSeek-R1作为超大规模语言模型,其核心架构设计包含以下创新: 专家混合架构(MoE) 采用6710亿参数的混合专家架构(MoE),每个推理过程仅激活370亿参数,实现计算效率与资源利用率的突破性提升。 Transformer框架…...
二阶近似 是什么意思
二阶近似 是什么意思 一、二阶近似的概念与举例 二阶近似是数学分析中通过泰勒展开对函数进行近似的方法,保留到二阶项(即包含一阶导数和二阶导数)。在优化问题(如模型训练)中,常用于近似损失函数,帮助更精准地更新模型参数。 举例: 假设损失函数为 L ( θ ) \mathc…...
STM32U575RIT6单片机(四)
作业: 使用I2C获取SHT20传感器温湿度 使用I2C获取AP3216C三合一传感器: 光照, 接近, 红外 三个功能 合并的传感器 #ifndef SHT20_H #define SHT20_H#include "stdint.h" #include "i2c.h" #include "stdio.h" //1、确定从机的设备地址(代码不…...
deepseek内网离线部署手册
前言 在当下 AI 浪潮汹涌的时代,DeepSeek 以其卓越的性能和出色的表现,迅速成为了众多专业人士和科技爱好者热议的焦点工具。在众多AI大模型的比拼中,DeepSeek 展现出了优越的实力。然而,对于许多企业和组织而言,出于…...
第七次作业,网络防御高级
拓扑:1 接口ip配置和区域划分: fw1: [fw1]interface GigabitEthernet 0/0/0 [fw1-GigabitEthernet0/0/0]service-manage all permit [fw1]firewall zone trust [fw1-zone-trust]add interface GigabitEthernet 1/0/0 [fw1]securi…...
“AIGC”狂飙:一场正在撕裂传统产业链的“智能革命”
前言 在过去的几年中,人工智能技术经历了飞速的发展,深刻地改变了全球科技产业的格局。2023年被称为“AI元年”,这一年的标志性事件包括ChatGPT的发布和大语言模型的快速崛起,这些技术突破不仅引发了全球范围内的关注,…...
【鸿蒙开发】Hi3861学习笔记- 定时器中断
00. 目录 文章目录 00. 目录01. 概述02. 定时器相关API2.1 hi_timer_create2.2 hi_timer_start2.3 hi_timer_stop2.4 hi_timer_delete 03. 硬件设计04. 软件设计05. 实验现象06. 附录 01. 概述 定时器,顾名思义就是用来计时的,我们常常会设定计时或闹钟…...
GB9706.1-2020部分定义
一、可拆卸的网电源连接器 1、器具耦合器 2、设备电源输入插口 3、可拆卸电源软电线 4、ME 设备 5、固定的网电源插座/多位插座(MSO) 6、网电源连接器 7、网电源插头 二、可拆卸的网电源连接 1、器具输入插座 2、患者连接 3、电线管 4、可拆卸电源软电线 5、外壳 6、固定…...
PCL 多项式拟合点法线(二维)
文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 这个思路其实很简单,假设我们有一组曲线点,我们可以对其拟合曲线并计算其导数来获取每个点的法向量,当然这一思路也可以扩展至三维。具体过程如下所示: 二、实现代码 PolyFit.h #pragma once/* ** 基于最小二乘…...
docker中安装Ghost报错Error: connect ECONNREFUSED 127.0.0.1:3306
今天尝试在docker中运行ghost报错 "Unknown database error"Error ID:500Error Code: ECONNREFUSED----------------------------------------Error: connect ECONNREFUSED 127.0.0.1:3306at /var/lib/ghost/versions/5.112.0/node_modules/knex-migrator/lib/datab…...
Java创造型模式之原型模式详解
设计模式是面向对象设计中的一种标准方法,用于解决常见的设计问题。原型设计模式(Prototype Pattern)是23种经典设计模式之一,属于创建型模式,它允许通过复制现有对象来创建新对象,而不是通过构造函数或工厂…...
3.17日Man2Marine
上游任务 vs. 下游任务 任务类型作用你的研究中的例子上游任务(Upstream Task)训练通用的音频表示,提供特征在大规模人类语音数据集上进行自监督学习下游任务(Downstream Task)利用上游任务学到的特征进行具体任务微调模型进行海洋哺乳动物叫声分类在 NLP(自然语言处理)…...
Fisher信息、梯度方差与学习率调度器的计算流程
Fisher信息、梯度方差与学习率调度器的计算流程 目录 Fisher信息、梯度方差与学习率调度器的计算流程**步骤1:定义模型与数据集****步骤2:计算梯度与Fisher信息****步骤3:计算梯度方差****步骤4:定义学习率调度器****步骤5:参数更新流程****示例输出****关键概念说明**步骤…...
209、不大于n的数的组合(python)
题目 已知一个数n和可组合的数字集合s,通过组合数字集合构成一个数x,使其不大于n。 例如: n 22356789 a [2, 3, 4, 8, 9] x 22349999 代码实现 n 22356789 a [2, 3, 4, 8, 9] a.sort() s str(n) tag True res [] for i in range…...
Matlab 汽车电子驻车系统仿真分析
1、内容简介 Matlab 176-汽车电子驻车系统仿真分析 可以交流、咨询、答疑 2、内容说明 略 摘 要: 论述了电子驻车制动控制系统的基本结构 、 组成及功能,并基于 Matlab/Simulink ,构建了包括直流电机 、 丝杠螺母及其内部 零件和相关摩擦力…...
蓝桥杯备考----模拟算法 phone number
嗯。这道题可以在两个和三个数字加-,我们只要随便输出一个奏行 那么!我们规范一下,我们尽可能的只在两个数字之间加,但是如果一共奇数个的话,我们就让最后三个成一组,也就是说,我们用的是个小贪…...
Unity WebGL IIS报错无法使用
Unity WebGL IIS报错无法使用 原因1:WebGL文件夹无访问权限 右键WebGL文件夹-属性 点击安全-编辑-添加 输入ever点击确定-应用即可...
【算法学习之路】11.并查集
并查集 前言一.简介二.基础并查集三.基础并查集题目12 四.种类并查集(扩展域并查集)五.种类并查集的题目 前言 我会将一些常用的算法以及对应的题单给写完,形成一套完整的算法体系,以及大量的各个难度的题目,目前算法也…...
第三课:Python递归编程艺术(从基础到优化)
递归,作为编程中一种优雅而强大的技术,以其简洁的代码风格和强大的问题解决能力,在算法设计中占据着举足轻重的地位。然而,递归的奥秘不仅仅在于其表面的简洁,更在于其背后的逻辑深度与优化技巧。本文将深入探讨递归编…...
插入排序程序并行化
一 插入排序 插入排序是稳定的原地排序算法,核心思想是逐步构建有序序列。对于未排序部分的每个元素,在已排序序列中从后向前扫描,找到合适位置插入。 二 并行化思路 1 分块排序 将数组分成多个子块,每个线程使用插入排序处理一块。 2 归并合并 将各有序子块归并成最终数…...
【系统架构设计师】操作系统 - 文件管理 ③ ( 树形目录结构 | 文件属性 | 绝对路径 与 相对路径 )
文章目录 一、树形目录结构1、树形目录结构 概念简介2、树形目录结构 组成3、文件属性4、树形目录结构 示例 二、绝对路径 与 相对路径1、绝对路径2、相对路径3、绝对路径 与 相对路径 对比 一、树形目录结构 1、树形目录结构 概念简介 " 树形目录结构 “ 又称为 ” 多级目…...
【量化科普】Standard Deviation,标准差
【量化科普】Standard Deviation,标准差 🚀量化软件开通 🚀量化实战教程 在量化投资领域,标准差(Standard Deviation)是一个非常重要的统计指标,用于衡量一组数据的离散程度。简单来说&#…...
实验三 Python 数据可视化 Python 聚类-K-means(CQUPT)
一、实验目的 Python 数据可视化: 1、学习使用 jieba、wordcloud 等类库生成词云图。 2、学习使用 Matplotlib 库进行数据可视化。 Python 聚类-K-means: 1、理解聚类非监督学习方法的基本原理。 2、掌握 Python、numpy、pandas、sklearn 实现聚类…...
东方通TongHttpServer:企业级服务代理中间件的卓越之选
随着信息技术的飞速发展,企业对于高性能、高安全性的中间件需求日益增长。东方通作为中国中间件领域的领军企业,凭借其在“安全”、“数据”和“智慧”三大产品体系上的深厚积累,推出了TongHttpServer(简称THS)&#x…...
EB-Cable许可证的常见问题及解决方案
在使用EB-Cable软件时,许可证问题可能是用户经常遇到的挑战之一。为了帮助用户更好地理解和解决许可证相关的问题,本文将列举一些常见的EB-Cable许可证问题,并提供相应的解决方案。 常见问题一:许可证激活失败 问题描述ÿ…...
ZED X系列双目3D相机的耐用性与创新设计解析
在工业自动化和学术研究领域,高精度的视觉设备正成为提升效率和质量的关键。ZED X系列AI立体相机,凭借其先进的技术和耐用的设计,为这一领域带来了新的可能。 核心技术:深度感知与精准追踪 ZED X系列的核心技术之一是Neural Dept…...
深入解析Java面向对象三大特征之多态、final、抽象类与接口
面向对象编程(OOP)的三大核心特征为封装、继承、多态,其中多态是最具灵活性和扩展性的特性。本文将从多态的本质出发,结合final关键字、抽象类与接口的设计,深入探讨这些概念的应用场景及其在代码中的实现细节…...
jmeter 循环控制器遍历列表中的数据
jmeter遍历列表中的数据并使用if控制器做相应的处理 测试场景请求获取列表接口发送请求JSON Extractor 提取对应字段 Loop Controller计数器If Controller 测试场景 请求获取列表接口使用循环控制器遍历接口,根据state字段判断是否发起其他请求 请求获取列表接口 …...
【Linux内核系列】:进程板块与文件板块的综合
🔥 本文专栏:Linux 🌸作者主页:努力努力再努力wz 💪 今日博客励志语录: 人生中成功只是一时的,失败却是人生的主旋律,但是如何面对失败却把人分成了不同的样子,有的人会被…...
深入理解嵌入式开发中的三个重要工具:零长度数组、container_of 和 typeof
在嵌入式开发中,内核开发者经常需要处理复杂的数据结构和动态内存分配。零长度数组、container_of 宏和 typeof 是内核开发中三个非常重要的工具,它们在结构体管理、内存操作和类型处理中发挥着关键作用。本文将详细探讨这三个工具的功能、应用场景及其在内核开发中的重要性。…...
第27周JavaSpringboot git初识
Git 课程笔记 一、Git 的介绍 1. Git 的诞生背景 Git 是 Linux 内核的作者 Linus Torvalds 为了更好地管理 Linux 内核开发而创建的版本控制系统。在 Linux 内核开发初期,由于开发者众多,协作成本很高,后来使用了 BitKeeper 工具来辅助协作…...
实战2. 利用Pytorch解决 CIFAR 数据集中的图像分类为 10 类的问题——提高精度
实战2. 利用Pytorch解决 CIFAR 数据集中的图像分类为 10 类的问题——提高精度 前期准备加载数据建立模型模型训练质量指标 让我们回到图像分类问题 CIFAR。 你的主要任务:实现整个模型训练流程,并在测试样本上获得良好的准确度指标值。 任务积分&#…...
CentOS高性能数据处理优化指南
在CentOS系统中,为实现高性能数据处理,需要从多个层面进行优化,包括系统配置、内核参数、硬件资源管理及软件优化等。 一、硬件优化 选择合适硬件是优化性能的基础,大规模数据处理任务建议使用多核CPU、大容量内存、SSD存储以及…...
深度剖析:Pytest Fixtures如何重塑自动化测试的可读性与高效性
关注开源优测不迷路 大数据测试过程、策略及挑战 测试框架原理,构建成功的基石 在自动化测试工作之前,你应该知道的10条建议 在自动化测试中,重要的不是工具 在编写单元测试时,是否发现自己写了很多相同/相似代码呢? 像…...
AIP-181 稳定级别
编号181原文链接AIP-181: Stability levels状态批准创建日期2019-02-18更新日期2019-02-18 虽然不同组织(谷歌或其他组织)拥有不同的产品生命周期,AIP使用以下术语指代API组件 稳定性 。 注意 这些稳定级别大致对应于Google Cloud中的产品发…...
比较 (leetcode 452. 用最少数量的箭引爆气球 leetcode 435. 无重叠区间 leetcode 56. 合并区间
leetcode系列 文章目录 一、射箭引爆气球二、无重叠区间三、合并区间总结 提示:小白个人理解,如有错误敬请谅解! 对于此类题目,都先按左区间排序,之后根据重叠还是不重叠来进行操作 一、射箭引爆气球 找到最大的重叠…...
什么是有限元力学?分而治之,将复杂问题转化为可计算的数学模型
有限元力学是应用有限元方法(Finite Element Method, FEM)解决力学问题的学科,属于计算力学的重要分支。它通过将复杂的连续体结构离散化为有限个简单单元的组合,结合数学和物理原理,近似求解力学行为(如应…...