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

进程与线程

  进程和线程

进程

基本概念

  • 进程:程序动态执行的过程,包括创建、调度和消亡。
  • 程序:存放在外存中的一段数据的集合。

进程创建

  • 进程空间分布
    每个进程运行后,操作系统为其分配0-4GB的虚拟内存空间。
    进程空间分为用户空间内核空间(不允许用户访问)。

  • 用户空间组成

    • 文本段(代码区):存放程序代码和指令。

    • 数据段(数据区)

      • 字符串常量
      • 已初始化的全局变量/静态变量
      • 未初始化的全局变量/静态变量(默认值为0)
        特点:
      • 编译时分配空间
      • 程序结束时回收
    • 系统数据段

      • 堆区:通过malloc申请空间,free释放空间。
        • 需避免内存泄漏
      • 栈区:存放局部变量和函数运行时数据。
        特点:
        • 未初始化时为随机值
        • 变量定义时分配空间
        • 超出作用域后自动回收

       

2. 虚拟地址和物理地址:

  • 1. 虚拟地址: 所有能够被用户看到的地址均为虚拟地址,表示用户可以寻址的范围
  • 2. 物理地址: 内存存放数据对应的实际硬件物理地址
  • 3. MMU: 实际地址和物理地址之间的映射由内存映射单元(MMU)完成

3. 多进程存储:

1. 存储方式:

  • 多个进程空间在操作系统中存储时,空间是独立的(物理地址是独立的)
  • 多个进程在操作系统中共用同一个虚拟内存空间(虚拟地址是共享的)

4. 多进程的调度:

1. 常见的进程调度算法:

  • 1. 先来先执行,后来后执行
  • 2. 高优先级调度算法
  • 3. 时间片轮转调度算法

2. 多进程调度本质:

宏观并行,微观串行

宏观分析:

一个CPU同时执行多个进程任务

微观分析:

一个CPU在多个任务中高速切换保障多任务执行

5. 进程相关的命令:

1. top

2. ps -ef

3. ps -aux

4. 后台执行进程任务

5. jobs

6. fg

7. nice/renice

8. kill/killall

6.  进程的状态:

3.进程相关函数接口

1. fork

注意:
  • 子进程拷贝父进程文本段、数据段、系统数据段
  • 进程的PID不一样
  • fork的返回值不一样,父进程中返回子进程的PID,子进程中返回0
  • PID:一定是 > 0

示例

#include "../head.h"int main(void)
{pid_t pid1;pid_t pid2;pid1 = fork();if (-1 == pid1){perror("fail to fork");return -1;}if (0 == pid1){printf("子进程1 PID:%d, PPID:%d\n", getpid(), getppid());}else if (pid1 > 0){pid2 = fork();if (-1 == pid2){perror("fail to fork");return -1;}if (0 == pid2){printf("子进程2 PID:%d, PPID:%d\n", getpid(), getppid());}else if (pid2 > 0){printf("父进程 PID:%d, child1PID:%d, child2PID:%d\n", getpid(), pid1, pid2);}}return 0;
}

2. getpid和getppid

示例

#include "../head.h"int main(void)
{pid_t pid1;pid_t pid2;pid1 = fork();if (-1 == pid1){perror("fail to fork");return -1;}if (0 == pid1){printf("子进程1 PID:%d, PPID:%d\n", getpid(), getppid());}else if (pid1 > 0){pid2 = fork();if (-1 == pid2){perror("fail to fork");return -1;}if (0 == pid2){printf("子进程2 PID:%d, PPID:%d\n", getpid(), getppid());}else if (pid2 > 0){printf("父进程 PID:%d, child1PID:%d, child2PID:%d\n", getpid(), pid1, pid2);}}return 0;
}

3. exit与_exit

注意:
  • 在主函数中调用exit和return功能保持一致
  • return在函数内部将结束该函数
  • exit在函数内部会将进程结束
  • exit会在结束前刷新缓存区
  • _exit不会刷新缓存区

4. 进程回收:

wait

注意:
  • wait具有阻塞等待功能,等到有子进程结束才会回收子进程继续向下执行
  • wait可以实现父子进程任务的同步

waitpid

注意:
  • waitpid可以非阻塞回收子进程空间
  • waitpid可以回收指定子进程空间

4.进程消亡

1. 孤儿进程:

父进程先结束,子进程会成为孤儿进程,被init进程收养

2. 僵尸进程:

是每个进程结束必然会经历的阶段

产生原因:

子进程结束后,父进程没有回收子进程空间,导致进程执行结束,空间依然被占用的状 态,称为僵尸进程

如何避免产生僵尸进程?

  • 父进程先结束,子进程会成为孤儿进程,孤儿进程被init进程收养,子进程再结束,init 进程回收进程空间
  • 子进程结束,父进程回收子进程空间,避免产生僵尸进程

5.exec函数族

1. exec函数族

  • 利用进程空间执行另一份代码
  • exec常搭配fork使用,fork负责创建新的子进程,exec负责让子进程执行自己的代码

#include "../head.h"int main(void)
{char *parg[5] = {"./hello","how","are","you",NULL,};printf("execl上面\n");
//    execl("./hello", "./hello", "how", "are", "you", NULL);execv(parg[0], parg);printf("看到我表示exec出错了\n");printf("execl下面\n");return 0;
}

2. 主函数传参

1. 主函数形式:

3. system函数:

#include "../head.h"void mysystem(void)
{pid_t pid;pid = fork();if(-1 == pid){perror("fail to fork");return;}if (0 == pid){execlp("ls", "ls", "-l", NULL);}wait(NULL);return;
}int main(void)
{printf("system上面\n");mysystem();printf("system下面\n");return 0;
}
int main() {if (fork() == 0) { /子进程/ }
pthread_t tid;else { /父进程/ }
pthread_create(&tid, NULL, task, NULL);}
}```
```
关键差异说明

性能差异
多线程的创建和切换速度更快,适合频繁创建的任务。多进程由于独立的地址空间,更适合需要高安全性和稳定性的场景。

资源共享
线程默认共享堆、全局变量和文件描述符,进程需要显式共享(如通过 shmgetmmap)。

调试难度
多线程因共享数据可能导致竞态条件(Race Condition),调试复杂度较高;多进程的隔离性使得问题更容易定位。

线程

 1.基本概念:

  • 线程是一个轻量级的进程
  • 线程本质就是一个进程
  • 线程和进程不完全一致,轻量指空间,进程空间和线程空间管理方法不同

2.进程和线程区别:

1. 线程本质

是进程,线程是任务创建、调度、回收的过程

2. 进程空间:

文本段 + 数据段 + 系统数据段

3. 线程空间:

  • 线程必须位于进程空间内部,没有进程,线程无法独立存在
  • 一个进程中的所有线程共享文本段+数据段+堆区,独享栈区
  • 线程独享的栈区默认为8M
  • 一个进程中的多个线程切换调度任务时,资源开销比较小

4. 进程和线程区别:

  • 线程是CPU任务调度的最小单元
  • 进程是操作系统资源分配的最小单元

3.多进程和多线程的优缺点

1. 多线程和多进程对比:

多线程 vs 多进程对比(C语言)
特性多线程(Thread)多进程(Process)
创建方式使用 pthread_create(POSIX线程)使用 forkexec 系列函数
资源开销轻量级,共享同一进程的内存空间重量级,独立内存空间,复制父进程资源(写时复制)
通信机制共享全局变量、互斥锁(pthread_mutex_t)、条件变量管道(pipe)、共享内存、信号、套接字、消息队列
同步机制互斥锁、信号量、读写锁文件锁、信号量、信号
数据隔离性线程共享数据,需同步避免竞争进程数据默认隔离,共享需显式操作
崩溃影响线程崩溃可能导致整个进程终止单个进程崩溃不影响其他进程
上下文切换成本低(共享地址空间)高(需切换内存映射表)
适用场景CPU密集型任务(需并行计算)、高响应需求需要高稳定性、隔离性的任务(如服务隔离)
示例代码```c```c
#include <pthread.h>#include <unistd.h>
void* task(void* arg) { /.../ }int main() {

跨平台兼容性
多进程(如 fork)在 Unix/Linux 系统支持良好,Windows 需使用 CreateProcess;多线程的 pthread 需注意平台实现差异。

4.线程的调度:

  • 调度保持一致
  • 宏观并行,微观串行

5.线程的消亡:

线程结束需要回收线程空间,否则产生僵尸线程

6.线程的函数接口

1. 函数接口:

2. pthread_create

3. pthread_self

4. pthread_exit

5. pthread_join

注意:

tid对应的线程只要不退出,pthread_join阻塞等待结束回收线程空间 pthread_join具备同步功能

7.线程传参

1. pthread_create

可以通过pthread_create第四个参数实现对线程内部的传参

 #include "../head.h"/* 线程参数类型 */typedef struct pthread_arg{pthread_t tid;                  
char threadname[32];            
int sleeptime;                  
}pthread_arg_t;//存放线程ID//线程名称
//睡眠时间
void *thread(void *arg){pthread_arg_t *parg = arg;printf("%s(TID:%#lx)开始执行\n", parg->threadname, parg->tid);while (1){printf("%s正在执行\n", parg->threadname);sleep(parg->sleeptime);}return NULL;}int main(void){   
int i = 0;pthread_arg_t args[4] = {{0, "采集线程", 1},{0, "存储线程", 2},{0, "显示线程", 5},{0, "日志线程", 10},};for (i = 0; i < 4; i++){pthread_create(&args[i].tid, NULL, thread, &args[i]);}for (i = 0; i < 4; i++){pthread_join(args[i].tid, NULL);}return 0;}

8.线程属性

1. 线程属性:

加入属性:

线程结束需要pthread_join手动回收

分离属性:

线程结束后系统自动回收线程空间

2. 函数接口:

1. pthread_attr_init

2.pthread_attr_setdetachstate

3. pthread_attr_destroy

3. 区别:

1. 分离属性:

  • 线程结束后,操作系统自动回收空间
  • 不需要手动调用pthread_join回收线程空间

2. 加入属性:

  • 可以回收到线程结束的状态
  • 可以完成线程间的同步

3.线程间通信:

1.概念:

多个线程间传递信息

2.方式:

采用全局变量

原因:

  • 进程是操作系统资源分配的最小单元
  • 每个进程空间独立的,包含文本段+数据段(全局变量)+系统数据段
  • 一个进程中的多个线程独享栈空间,文本段、数据段、堆区进程多线程共享

注意:

多线程同时操作共享空间会引发资源竞争,需要加上互斥锁解决资源竞争问题

3.互斥锁:

1. 概念

  • 解决资源竞争的一种方式,可以看成是一种资源。
  • 只能加锁一次,加锁期间不能再次加锁,也不能强制占有一个已经加锁的锁资源,必须等待锁 资源释放,也就是解锁后才能继续操作该锁
  • 加锁和解锁中间的代码称为临界代码,也称为临界区
  • 只能防止多个线程对资源的竞争,不能决定代码的先后执行顺序
  • 原子操作:CPU执行原子操作时无法切换调度任务

2. 使用方式:

  • 1. 定义互斥锁(全局变量)
  • 2. 对锁初始化
  • 3. 操作全局资源前先加锁
  • 4. 如果加锁成功则完成对全局资源操作
  • 5. 如果加锁失败则表示有人占用资源,必须等待其余人释放锁资源才能加锁成功
  • 6. 直到加锁成功使用该全局资源

3. 函数接口:

1. pthread_mutex_init

2. pthread_mutex_lock

3. pthread_mutex_unlock

4. pthread_mutex_destroy

4.死锁

1. 概念:

多线程由于加锁解锁错误导致程序无法继续向下运行的状态称为死锁状态,简称为死锁

2. 死锁产生的四个必要条件:

  • 1. 互斥条件
  • 2. 不可剥夺条件
  • 3. 请求保持条件
  • 4. 循环等待条件

3. 如何避免死锁:

  • 1. 加锁顺序保持一致
  • 2. 使用pthread_mutex_trylock替换pthread_mutex_lock

5.信号量

1. 概念:

  • 信号量是一种资源
  • 信号量只能完成四种操作:初始化、销毁、申请、释放
  • 如果信号量资源数为0,申请资源会阻塞等待,直到占用资源的任务释放资源,资源数不为0时 才能申请到资源并继续向下执行
  • 释放资源不会阻塞

2. 函数接口:

1. sem_init

2. sem_destroy

3. sem_wait

注意:
  • 申请信号量会让信号量资源数-1
  • 如果信号量资源数为0,则会阻塞等待,直到有任务释放资源,才能拿到资源并继续向下执行
4. sem_post

 进程间通信(IPC)概述

进程间通信(Inter-Process Communication, IPC)是Linux系统中多个进程共享数据或同步操作的机制。Linux提供了多种IPC方式,包括管道、消息队列、共享内存、信号量、套接字等。每种方式适用于不同场景,需根据需求选择。


管道(Pipe)

管道是半双工的通信方式,数据单向流动,通常用于父子进程或兄弟进程间的通信。

匿名管道

通过pipe()系统调用创建,返回两个文件描述符:fd[0](读端)和fd[1](写端)。

代码示例

#include <unistd.h>
#include <stdio.h>int main() {int fd[2];pid_t pid;char buf[256];if (pipe(fd) == -1) {perror("pipe");return 1;}pid = fork();if (pid == 0) { // 子进程close(fd[1]); // 关闭写端read(fd[0], buf, sizeof(buf));printf("Child received: %s\n", buf);close(fd[0]);} else { // 父进程close(fd[0]); // 关闭读端write(fd[1], "Hello from parent", 18);close(fd[1]);}return 0;
}

命名管道(FIFO)

通过mkfifo命令或mkfifo()函数创建,允许无亲缘关系的进程通信。

代码示例

#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>int main() {mkfifo("/tmp/myfifo", 0666);int fd = open("/tmp/myfifo", O_WRONLY);write(fd, "Hello FIFO", 11);close(fd);return 0;
}


消息队列(Message Queue)

消息队列是内核维护的链表,进程通过唯一标识符(key)访问队列,支持不同类型消息。

常用函数
  • msgget():创建或获取队列。
  • msgsnd():发送消息。
  • msgrcv():接收消息。
  • msgctl():控制队列(如删除)。

代码示例

#include <sys/msg.h>
#include <stdio.h>
#include <string.h>struct msg_buffer {long msg_type;char msg_text[100];
};int main() {key_t key = ftok("progfile", 65);int msgid = msgget(key, 0666 | IPC_CREAT);struct msg_buffer message;message.msg_type = 1;strcpy(message.msg_text, "Hello Message Queue");msgsnd(msgid, &message, sizeof(message), 0);printf("Sent: %s\n", message.msg_text);msgrcv(msgid, &message, sizeof(message), 1, 0);printf("Received: %s\n", message.msg_text);msgctl(msgid, IPC_RMID, NULL);return 0;
}


共享内存(Shared Memory)

共享内存是最快的IPC方式,允许多个进程访问同一块内存区域。

常用函数
  • shmget():创建或获取共享内存段。
  • shmat():附加到进程地址空间。
  • shmdt():分离共享内存。
  • shmctl():控制共享内存(如删除)。

代码示例

#include <sys/shm.h>
#include <stdio.h>
#include <string.h>int main() {key_t key = ftok("shmfile", 65);int shmid = shmget(key, 1024, 0666 | IPC_CREAT);char *str = (char*)shmat(shmid, NULL, 0);printf("Write data: ");fgets(str, 1024, stdin);printf("Data written: %s\n", str);shmdt(str);shmctl(shmid, IPC_RMID, NULL);return 0;
}


信号量(Semaphore)

信号量用于同步进程对共享资源的访问,避免竞态条件。

常用函数
  • semget():创建或获取信号量集。
  • semop():执行原子操作(如P/V操作)。
  • semctl():控制信号量(如初始化或删除)。

代码示例

#include <sys/sem.h>
#include <stdio.h>
#include <unistd.h>union semun {int val;struct semid_ds *buf;unsigned short *array;
};void p(int semid) {struct sembuf op = {0, -1, SEM_UNDO};semop(semid, &op, 1);
}void v(int semid) {struct sembuf op = {0, 1, SEM_UNDO};semop(semid, &op, 1);
}int main() {key_t key = ftok("semfile", 65);int semid = semget(key, 1, 0666 | IPC_CREAT);union semun arg;arg.val = 1;semctl(semid, 0, SETVAL, arg);pid_t pid = fork();if (pid == 0) {p(semid);printf("Child enters critical section\n");sleep(2);printf("Child leaves critical section\n");v(semid);} else {p(semid);printf("Parent enters critical section\n");sleep(2);printf("Parent leaves critical section\n");v(semid);}semctl(semid, 0, IPC_RMID);return 0;
}


套接字(Socket)

套接字支持跨网络通信,也可用于本地进程间通信(AF_UNIX域)。

本地套接字示例
#include <sys/socket.h>
#include <sys/un.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>int main() {int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);struct sockaddr_un addr;addr.sun_family = AF_UNIX;strcpy(addr.sun_path, "/tmp/socket");bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));listen(sockfd, 5);int client = accept(sockfd, NULL, NULL);char buf[256];read(client, buf, sizeof(buf));printf("Server received: %s\n", buf);write(client, "Hello from server", 17);close(client);close(sockfd);unlink("/tmp/socket");return 0;
}


信号(Signal)

信号是异步通知机制,用于进程间简单事件通知(如终止、中断)。

常用函数
  • kill():发送信号。
  • signal():注册信号处理函数。

代码示例

#include <signal.h>
#include <stdio.h>
#include <unistd.h>void handler(int sig) {printf("Received signal: %d\n", sig);
}int main() {signal(SIGINT, handler);printf("Press Ctrl+C to trigger signal\n");pause(); // 等待信号return 0;
}


对比与选择建议

IPC方式适用场景特点
管道父子进程简单通信单向,容量有限
消息队列无亲缘关系进程结构化通信支持消息类型,内核持久化
共享内存高频大数据量通信无需拷贝,需同步机制
信号量资源访问同步计数器,避免竞态
套接字跨网络或复杂通信灵活,支持多种协议
信号异步事件通知简单,不可靠

根据需求选择:

  • 高性能:共享内存 + 信号量。
  • 结构化数据:消息队列。
  • 简单通知:信号或管道。
  • 跨机器通信:套接字。

注意事项

  1. 同步问题:共享内存和消息队列需配合信号量避免竞态。
  2. 资源释放:显式删除IPC对象(如msgctlshmctl)。
  3. 权限控制:设置正确的访问权限(如0666)。
  4. 错误处理:检查系统调用返回值(如-1表示失败)。
http://www.xdnf.cn/news/18391.html

相关文章:

  • langchain的简单应用案例---(1)使用langchain构建本地知识库
  • K近邻算法(knn)
  • 基于 RxJava 构建强大的 Android 文件下载管理器
  • Android SystemServer 中 Service 的创建和启动方式
  • AI与大数据驱动下的食堂采购系统源码:供应链管理平台的未来发展
  • Git#cherry-pick
  • QT示例 基于Subdiv2D的Voronoi图实现鼠标点击屏幕碎裂掉落特效
  • Day22 顺序表与链表的实现及应用(含字典功能与操作对比)
  • 服务器无公网ip如何对外提供服务?本地网络只有内网IP,如何能被外网访问?
  • Vue.prototype 的作用
  • JUC之CompletableFuture【中】
  • Redis Reactor 模型详解【基本架构、事件循环机制、结合源码详细追踪读写请求从客户端连接到命令执行的完整流程】
  • FPGA 在情绪识别领域的护理应用(一)
  • 论文阅读系列(一)Qwen-Image Technical Report
  • 中和农信如何打通农业科技普惠“最后一百米”
  • 企业架构是什么?解读
  • 通过分布式系统的视角看Kafka
  • python黑盒包装
  • Matplotlib数据可视化实战:Matplotlib图表注释与美化入门
  • 抓取手机游戏相关数据
  • LWIP流程全解
  • java实现url 生成二维码, 包括可叠加 logo、改变颜色、设置背景颜色、背景图等功能,完整代码示例
  • 【运维进阶】Ansible 角色管理
  • 记一次 .NET 某自动化智能制造软件 卡死分析
  • 流程进阶——解读 49页 2023 IBM流程管理与变革赋能【附全文阅读】
  • Redis缓存加速测试数据交互:从前缀键清理到前沿性能革命
  • 微服务-07.微服务拆分-微服务项目结构说明
  • 236. 二叉树的最近公共祖先
  • 从密度到聚类:DBSCAN算法的第一性原理解析
  • 100202Title和Input组件_编辑器-react-仿低代码平台项目