当前位置: 首页 > news >正文

操作系统重点

进程与线程:区别、通信方式、同步方式

1. 进程 vs 线程:根本区别

基本概念

  • 进程:程序的一次执行实例,是资源分配的基本单位

  • 线程:进程内的一个执行流,是CPU调度的基本单位

内存布局对比

进程内存布局:
+-----------------------+
|      栈区 (Stack)     | ← 每个线程有独立栈
+-----------------------+
|          ↓            |
|                       |
|         堆区 (Heap)    | ← 共享
+-----------------------+
|         ↑             |
|                       |
+-----------------------+
|   全局/静态数据区       | ← 共享
+-----------------------+
|       代码段          | ← 共享
+-----------------------+线程共享:代码段、数据段、堆、文件描述符、信号处理等
线程独有:栈、寄存器、程序计数器、线程局部存储

详细区别对比表

特性进程线程
资源分配操作系统分配独立资源共享进程资源
内存空间独立地址空间共享地址空间
创建开销大(需要分配资源)小(共享资源)
上下文切换开销大(需要切换页表等)开销小(只需切换寄存器)
通信方式复杂(IPC机制)简单(共享内存即可)
稳定性一个进程崩溃不影响其他进程一个线程崩溃会导致整个进程崩溃
数据共享需要显式IPC机制天然共享进程数据
独立性完全独立相互依赖

核心总结

  • 进程 是资源 ownership 的单位,为程序运行提供独立的沙盒环境。

  • 线程 是执行 schedule 的单位,是CPU真正调度和分派的基本单元,共享进程的资源以实现高效协作。

编程:

1. 创建进程 (使用 fork())

fork() 是Unix-like系统中创建新进程的主要方法。它会复制当前进程(父进程),创建一个几乎完全相同的子进程。

#include <iostream>
#include <unistd.h> // for fork(), getpid()
#include <sys/wait.h> // for wait()int main() {pid_t pid = fork(); // 创建子进程if (pid < 0) {// fork失败std::cerr << "Fork failed!" << std::endl;return 1;} else if (pid == 0) {// 这里是子进程的代码std::cout << "Hello from Child Process! My PID is: " << getpid() << std::endl;sleep(2); // 模拟子进程工作std::cout << "Child process exiting." << std::endl;} else {// 这里是父进程的代码 (pid > 0, pid是子进程的ID)std::cout << "Hello from Parent Process! I created a child with PID: " << pid << std::endl;wait(nullptr); // 等待子进程结束std::cout << "Parent process resumed after child exited." << std::endl;}return 0;
}
2. 创建线程 (使用 C++11 std::thread)

C++11提供了标准的线程库,跨平台且易于使用

#include <iostream>
#include <thread>
#include <chrono>// 线程要执行的函数
void thread_function(int id) {std::cout << "Thread " << id << " is starting..." << std::endl;std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟工作std::cout << "Thread " << id << " is finishing..." << std::endl;
}int main() {std::cout << "Main thread started." << std::endl;// 创建并启动两个线程std::thread t1(thread_function, 1);std::thread t2(thread_function, 2);// 等待线程结束t1.join();t2.join();std::cout << "Main thread finished." << std::endl;return 0;
}

2. 进程间通信(IPC)方式

由于进程地址空间独立,无法直接共享变量,因此需要操作系统提供特殊的机制来通信。

1. 管道 (Pipe)
  • 概念:一种半双工的通信方式,数据只能单向流动。通常用于有亲缘关系的进程(如父子进程)。

  • 原理:在内存中创建一个内核缓冲区,像一个队列。一个进程写,另一个进程读。

  • 类型

    • 匿名管道 (Anonymous Pipe):只能在父子进程间使用。

    • 命名管道 (Named Pipe / FIFO):提供了一个路径名与之关联,允许无亲缘关系的进程通信。

  • 特点:简单,但效率较低,容量有限,且通信是单向的。如果需要双向通信,需要建立两个管道。

2. 消息队列 (Message Queue)
  • 概念:存放在内核中的消息链表。进程可以向队列中添加消息(写),或从队列中读取消息(读)。

  • 原理:每个消息是一个数据块,具有特定的格式和优先级。进程通过消息队列标识符来访问同一个队列。

  • 特点

    • 克服了管道只能承载无格式字节流的缺点。

    • 独立于发送和接收进程存在(进程终止后,消息队列及其内容并不会被删除)。

    • 避免了同步阻塞问题(可以非阻塞地读写)。

3. 共享内存 (Shared Memory)
  • 概念最快的IPC方式。允许多个进程共享同一块物理内存区域。

  • 原理

    1. 由一个进程创建一块共享内存段。

    2. 其他进程通过系统调用将其映射 (attach) 到自己的地址空间中。

    3. 之后,进程就可以像访问普通内存一样读写这块区域,从而直接交换数据。

  • 特点

    • 极快:数据不需要在内核和用户空间之间来回拷贝。

    • 需要同步:因为多个进程同时读写同一块内存,必须配合使用信号量互斥锁等同步机制来确保数据一致性。

其他方式:
  • 信号 (Signal):一种异步通信机制,用于通知接收进程某个事件已经发生(如 kill -9)。携带的信息量少。

  • 套接字 (Socket):最通用的通信机制,可用于不同机器上的进程间网络通信,也支持同一台主机上的进程通信。

编程:

2.1 管道(Pipe)

#include <iostream>
#include <unistd.h>
#include <string.h>int main() {int fd[2]; // fd[0]是读端,fd[1]是写端pipe(fd); // 创建匿名管道pid_t pid = fork();if (pid == 0) { // 子进程close(fd[0]); // 关闭子进程不需要的读端const char* msg = "Hello from child via pipe!";write(fd[1], msg, strlen(msg) + 1); // 向管道写入数据close(fd[1]);} else { // 父进程close(fd[1]); // 关闭父进程不需要的写端char buffer[100];read(fd[0], buffer, sizeof(buffer)); // 从管道读取数据std::cout << "Parent received: " << buffer << std::endl;close(fd[0]);wait(nullptr);}return 0;
}

特点:单向通信,有亲缘关系的进程间使用,容量有限

2.2 命名管道(FIFO)

命名管道允许无亲缘关系的进程通信。

fifo_writer.cpp (写端)

#include <iostream>
#include <fcntl.h>   // for open
#include <unistd.h>  // for write, close
#include <cstring>   // for strlen
#include <sys/stat.h> // for mkfifoint main() {const char* fifo_name = "/tmp/my_fifo";// 创建命名管道(如果不存在)mkfifo(fifo_name, 0666); // 权限 0666std::cout << "Opening FIFO for writing...\n";int fd = open(fifo_name, O_WRONLY); // 阻塞,直到有读端打开const char* message = "Hello from Writer Process!";write(fd, message, strlen(message) + 1);std::cout << "Message sent.\n";close(fd);// 通常不删除FIFO,以便多次使用// unlink(fifo_name);return 0;
}

fifo_reader.cpp (读端)

#include <iostream>
#include <fcntl.h>
#include <unistd.h>int main() {const char* fifo_name = "/tmp/my_fifo";char buffer[100];std::cout << "Opening FIFO for reading...\n";int fd = open(fifo_name, O_RDONLY); // 阻塞,直到有写端打开read(fd, buffer, sizeof(buffer));std::cout << "Received: " << buffer << std::endl;close(fd);return 0;
}

编译运行

g++ fifo_writer.cpp -o writer
g++ fifo_reader.cpp -o reader
# 先在一个终端运行 ./writer (它会阻塞等待读者)
# 在另一个终端运行 ./reader

特点:可用于无亲缘关系的进程,有文件名,持久化

2.3 消息队列(Message Queue)

msg_sender.cpp (发送端)

#include <iostream>
#include <sys/msg.h>
#include <cstring>// 定义消息结构体
struct message_buffer {long msg_type;char msg_text[100];
};int main() {key_t key = ftok("progfile", 65); // 生成唯一keyint msgid = msgget(key, 0666 | IPC_CREAT); // 创建/获取消息队列message_buffer msg;msg.msg_type = 1; // 消息类型strcpy(msg.msg_text, "Hello from Message Queue!");msgsnd(msgid, &msg, sizeof(msg.msg_text), 0); // 发送消息std::cout << "Message sent: " << msg.msg_text << std::endl;return 0;
}

msg_receiver.cpp (接收端)

#include <iostream>
#include <sys/msg.h>struct message_buffer {long msg_type;char msg_text[100];
};int main() {key_t key = ftok("progfile", 65);int msgid = msgget(key, 0666);message_buffer msg;msgrcv(msgid, &msg, sizeof(msg.msg_text), 1, 0); // 接收类型为1的消息std::cout << "Message received: " << msg.msg_text << std::endl;// 销毁消息队列msgctl(msgid, IPC_RMID, NULL);return 0;
}

编译运行

g++ msg_sender.cpp -o sender
g++ msg_receiver.cpp -o receiver
./sender
./receiver

特点:消息格式固定,支持优先级,独立于进程存在

2.4 共享内存 (Shared Memory) + 信号量 (Semaphore)

这是最经典的组合:共享内存负责高速数据传输,信号量负责同步。

shm_writer.cpp (写入端)

#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h> // 信号量头文件
#include <cstring>
#include <unistd.h>// 联合体,用于semctl初始化
union semun {int val;struct semid_ds *buf;unsigned short *array;
};int main() {key_t key = ftok("shmfile", 65);int shmid = shmget(key, 1024, 0666 | IPC_CREAT);char* str = (char*)shmat(shmid, (void*)0, 0);// 创建信号量key_t sem_key = ftok("semfile", 66);int semid = semget(sem_key, 1, 0666 | IPC_CREAT);union semun su;su.val = 1; // 信号量初始值为1 (互斥锁)semctl(semid, 0, SETVAL, su);struct sembuf sb = {0, -1, 0}; // P操作semop(semid, &sb, 1); // 加锁// 临界区:写入数据std::cout << "Write Data: ";std::cin.getline(str, 1024);std::cout << "Data written: " << str << std::endl;sb.sem_op = 1; // V操作semop(semid, &sb, 1); // 解锁shmdt(str);return 0;
}

shm_reader.cpp (读取端)

#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>union semun {int val;struct semid_ds *buf;unsigned short *array;
};int main() {key_t key = ftok("shmfile", 65);int shmid = shmget(key, 1024, 0666);char* str = (char*)shmat(shmid, (void*)0, 0);key_t sem_key = ftok("semfile", 66);int semid = semget(sem_key, 1, 0666);struct sembuf sb = {0, -1, 0}; // P操作semop(semid, &sb, 1); // 加锁// 临界区:读取数据std::cout << "Data read: " << str << std::endl;sb.sem_op = 1; // V操作semop(semid, &sb, 1); // 解锁shmdt(str);shmctl(shmid, IPC_RMID, NULL); // 销毁共享内存semctl(semid, 0, IPC_RMID);    // 销毁信号量return 0;
}

编译运行

g++ shm_writer.cpp -o shm_writer
g++ shm_reader.cpp -o shm_reader
./shm_writer
# 输入数据后,运行
./shm_reader

特点:最快的IPC方式,需要同步机制配合

2.5 信号 (Signal)

signal_example.cpp (发送信号)

#include <iostream>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>int main() {pid_t my_pid = getpid();std::cout << "My PID is: " << my_pid << std::endl;std::cout << "Sending SIGINT signal to myself in 3 seconds...\n";sleep(3);kill(my_pid, SIGINT); // 向自己发送中断信号std::cout << "This line may not be printed.\n";return 0;
}

signal_handler.cpp (捕获和处理信号)

#include <iostream>
#include <csignal>
#include <unistd.h>// 信号处理函数
void signal_handler(int signum) {std::cout << "\nInterrupt signal (" << signum << ") received.\n";// 进行清理操作exit(signum);
}int main() {// 注册信号SIGINT和信号处理函数signal(SIGINT, signal_handler);while (true) {std::cout << "Going to sleep... (Press Ctrl+C to interrupt)\n";sleep(1);}return 0;
}

编译运行

g++ signal_handler.cpp -o handler
./handler
# 然后按 Ctrl+C 观察效果

特点:异步通知机制,用于简单事件通知

2.6 套接字 (Socket) - Unix Domain Socket (本地)

socket_server.cpp (服务端)

#include <iostream>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <cstring>int main() {int server_fd, client_fd;struct sockaddr_un server_addr, client_addr;socklen_t client_len = sizeof(client_addr);char buffer[100];// 创建socketserver_fd = socket(AF_UNIX, SOCK_STREAM, 0);// 配置地址server_addr.sun_family = AF_UNIX;strcpy(server_addr.sun_path, "/tmp/demo_socket");unlink(server_addr.sun_path); // 确保文件不存在bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr));listen(server_fd, 5); // 开始监听std::cout << "Server waiting for connection...\n";client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len);read(client_fd, buffer, sizeof(buffer));std::cout << "Server received: " << buffer << std::endl;const char* reply = "Hello from Server!";write(client_fd, reply, strlen(reply) + 1);close(client_fd);close(server_fd);unlink(server_addr.sun_path);return 0;
}

socket_client.cpp (客户端)

#include <iostream>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <cstring>int main() {int sockfd;struct sockaddr_un server_addr;char buffer[100];sockfd = socket(AF_UNIX, SOCK_STREAM, 0);server_addr.sun_family = AF_UNIX;strcpy(server_addr.sun_path, "/tmp/demo_socket");connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr));const char* message = "Hello from Client!";write(sockfd, message, strlen(message) + 1);read(sockfd, buffer, sizeof(buffer));std::cout << "Client received: " << buffer << std::endl;close(sockfd);return 0;
}

编译运行

g++ socket_server.cpp -o server
g++ socket_client.cpp -o client
# 先运行 ./server (它会等待连接)
# 再另一个终端运行 ./client

特点:最通用的IPC方式,支持网络通信

 重要说明

  1. 错误处理:以上示例省略了错误处理以便突出重点,实际使用时务必检查每个系统调用的返回值!

  2. 编译:所有示例使用 g++ 编译,不需要特殊标志(除了需要 -pthread 的线程例子)。

  3. 运行顺序:注意有些示例需要按特定顺序运行(如先server后client,先writer后reader)。

3. 同步机制

当多个执行流(进程或线程)并发地访问共享资源时,为了防止出现竞态条件 (Race Condition) 和数据不一致,需要进行同步。

1. 互斥锁 (Mutex)
  • 概念:像一把钥匙,保护一个共享资源。一次只允许一个线程访问该资源。

  • 操作

    • 加锁 (Lock):如果锁已被占用,则当前线程会被阻塞,直到锁被释放。

    • 解锁 (Unlock):释放锁,允许其他线程获取它。

  • 特点:实现简单,是最常用的同步机制。锁的持有者必须负责释放锁,否则会导致死锁。

2. 信号量 (Semaphore)
  • 概念:一个计数器,用于控制访问多个共享资源的线程数。是比互斥锁更通用的同步原语。

  • 操作

    • P操作 (Wait):尝试减少信号量的值。如果值大于0,则减少并继续;如果值为0,则线程阻塞,直到值大于0。

    • V操作 (Signal):增加信号量的值,并唤醒一个等待的线程(如果有)。

  • 类型

    • 二进制信号量:值只有0和1,功能上等价于一个互斥锁

    • 计数信号量:值可以大于1,用于控制对一组 identical 资源的访问(例如,有5个打印机的打印池,信号量初始值为5)。

3. 条件变量 (Condition Variable)
  • 概念:允许线程在某些条件不满足时主动阻塞自己释放锁,等待其他线程改变条件后通知它。它总是与一个互斥锁结合使用。

  • 操作

    • wait(cond, mutex):线程阻塞自己,并原子性地释放互斥锁 mutex。被唤醒后,它会重新获取 mutex 再返回。

    • signal(cond):唤醒一个正在 wait 的线程。

    • broadcast(cond):唤醒所有正在 wait 的线程。

  • 使用场景:非常适合用于生产者-消费者模型。当缓冲区空时,消费者线程等待;生产者生产数据后,通知消费者。反之,当缓冲区满时,生产者等待;消费者消费数据后,通知生产者。

同步方式对比总结:
机制核心思想主要用途
互斥锁 (Mutex)独占访问,一次一个。保护临界区,确保对单个共享资源的互斥访问。用于简单的互斥
信号量 (Semaphore)计数器,控制N个访问。控制对一组数量有限的相同资源的访问。用于管理资源池
条件变量 (Condition)等待条件成立,与锁配合。用于线程间协调,当某个状态条件不满足时让线程等待。用于复杂的线程状态协调

//TODO:继续学习!!!


文章转载自:

http://bFRDrna8.trrpb.cn
http://oOOKm0we.trrpb.cn
http://nzMrQJ8H.trrpb.cn
http://NJsH4lVl.trrpb.cn
http://cPZFU3Wf.trrpb.cn
http://rii6MELB.trrpb.cn
http://9fxH2JoT.trrpb.cn
http://pnEhE5dY.trrpb.cn
http://HjvLsALi.trrpb.cn
http://qmxxIcYD.trrpb.cn
http://Iwp9ytDp.trrpb.cn
http://5qC2EQ5o.trrpb.cn
http://aN0btdG7.trrpb.cn
http://PXi12602.trrpb.cn
http://gnZjnt5c.trrpb.cn
http://ckDjJIGd.trrpb.cn
http://alGMJq4g.trrpb.cn
http://NNn8rIlX.trrpb.cn
http://qffSMlcw.trrpb.cn
http://9BfaWYGR.trrpb.cn
http://SrHHYpVl.trrpb.cn
http://EtpHsXNR.trrpb.cn
http://K2uJKsFU.trrpb.cn
http://LcJtjOg4.trrpb.cn
http://i19IxXCV.trrpb.cn
http://8zSbFj15.trrpb.cn
http://MI7O6Hpx.trrpb.cn
http://oRLiiOb9.trrpb.cn
http://Ynlju6f0.trrpb.cn
http://o1pstYQ8.trrpb.cn
http://www.dtcms.com/a/368970.html

相关文章:

  • 安全运维-云计算系统安全
  • HTML 各种标签的使用说明书
  • BYOFF (Bring Your Own Formatting Function)解析(80)
  • MySQL源码部署(rhel7)
  • HashMap多线程下的循环链表问题
  • 企业微信AI怎么用?食品集团靠它砍掉50%低效操作,答案就是选对企业微信服务商
  • 企业微信AI怎么用才高效?3大功能+5个实操场景,实测效率提升50%
  • Arduino Nano33 BLESense Rev2【室内空气质量检测语音识别蓝牙调光台灯】
  • 无人机小目标检测新SOTA:MASF-YOLO重磅开源,多模块协同助力精度飞跃
  • 本地 Docker 环境 Solr 配置 SSL 证书
  • SQL中TRUNCATE vs. DELETE 命令对比
  • RequestContextFilter介绍
  • [密码学实战](GBT 15843.3)基于SM2数字签名的实体鉴别实现完整源码(四十九)
  • 【UE】 实现指向性菲涅尔 常用于圆柱体的特殊菲涅尔
  • 标签系统的架构设计与实现
  • 卫星在轨光压计算详解
  • 摄像头模块的种类:按结构分类
  • 第8篇:决策树与随机森林:从零实现到调参实战
  • 迁移学习-ResNet
  • CentOS安装或升级protoc
  • 【QT 5.12.12 下载 Windows 版本】
  • 多语言Qt Linguist
  • 2025年- H118-Lc86. 分隔链表(链表)--Java版
  • 快速了解迁移学习
  • 【HEMCO第一期】用户教程
  • SVT-AV1编码器中实现WPP依赖管理核心调度
  • Qt---JSON处理体系
  • 基于YOLOv8的车辆轨迹识别与目标检测研究分析软件源代码+详细文档
  • 行业了解06:物流运输业
  • 碰一碰系统+手机端全线一站式开发源码技术saas搭建步骤: