如何理解进程和线程之间的关系

news2024/11/15 19:54:48

目录

前言

一、进程和线程的关系

        1、引入线程的原因

 2、线程的特点

 3、线程和进程的关系

二、如何在进程中创建线程

 1、创建线程的函数

         2、举例使用:

 三、线程间的同步互斥机制

        1、什么是同步互斥机制 

 2、如何在线程中使用同步互斥机制

 3、实际举例

总结 


前言

        线程的引入是为了充分利用现代计算机的多核处理能力,提高程序的效率、响应速度和资源利用率。

一、进程和线程的关系

        1、引入线程的原因

  • 并发执行

    • 线程允许一个进程中同时执行多个任务。通过在同一个进程内创建多个线程,可以实现并发处理,从而提高程序的效率和响应速度。例如,一个程序可以在一个线程中处理用户界面响应,在另一个线程中进行后台数据处理。
  • 资源共享

    • 线程共享进程的资源(如内存空间和文件描述符),这使得线程间的通信和数据共享比进程间更加高效和简单。线程之间可以轻松共享数据和资源,而不需要像进程间那样复杂的通信机制(如管道或消息队列)

  • 减少开销

    • 创建和管理线程比创建和管理进程的开销要小得多。线程的创建和销毁成本较低,因为线程共享同一进程的地址空间和资源,不需要像进程那样进行内存映射和管理。
  • 提高响应性

    • 在多线程程序中,某个线程的任务(如I/O操作)不会阻塞其他线程的执行。这意味着即使一个线程正在等待某些操作完成,其他线程仍然可以继续运行,从而保持程序的响应性。
  • 更好的资源利用
    • 在多核处理器上,多线程程序可以将多个线程分配到不同的处理器核心上,从而提高程序的并行处理能力。这种并行执行能够更有效地利用计算资源,提升性能。

 2、线程的特点

  • 在同一个进程中的多个线程共享进程资源
  • 在进程中执行的代码叫主线程,线程执行的代码叫子线程
  • 一个进程中至少有一个主线程,可以有0个线程 

 3、线程和进程的关系

        线程和进程是操作系统中两个重要的概念,它们之间有着密切的关系,但又有各自独特的特点。以下是对线程和进程关系的讲述:

         进程(Process):
1. **定义**:
           - 进程是操作系统中资源分配的基本单位。它是一个正在运行的程序的实例,包括程序代码、数据、堆栈、寄存器状态以及进程控制块等信息。

2. **资源管理**:
           - 每个进程都有自己的独立地址空间、堆栈、文件描述符等资源。进程间的数据共享和通信需要通过特定的机制,如管道、消息队列、共享内存等。

3. **独立性**:
           - 进程是相互独立的,一个进程的崩溃通常不会直接影响其他进程。每个进程在内存中都有自己的地址空间。

        线程(Thread):
1. **定义**:
           - 线程是进程中的一个执行单元,是进程内部的最小调度单位。一个进程可以包含一个或多个线程,这些线程共享进程的资源,但各自有自己的执行路径和栈。

2. **资源共享**:
           - 同一进程中的所有线程共享该进程的地址空间和资源,如内存、文件描述符等。这使得线程间的通信和数据共享更高效,因为它们不需要像进程间那样使用复杂的通信机制。

3. **轻量级**:
           - 线程的创建和销毁成本较低,相比于进程,它们在切换上下文时的开销也较小,因为线程之间共享相同的进程资源,不需要重新分配内存。

### 关系总结:
- **包含关系**:
          - 一个进程可以包含多个线程。线程是进程内部的执行单元,而进程是管理和分配资源的单位。

- **资源共享**:
          - 同一进程中的线程共享进程的资源,如内存和文件描述符。而不同进程之间则拥有独立的资源空间,资源共享和通信更复杂。

- **独立性与并发**:
          - 进程之间是相互独立的,进程崩溃通常不会直接影响其他进程。线程之间则可以并发执行,共享资源,使得多线程的程序能够提高效率和响应性,但也带来多线程同步和安全的问题。

- **开销与效率**:
          - 线程的创建和管理开销小于进程,因为线程共享进程资源,无需分配和管理新的地址空间。进程的开销较大,但提供了更好的隔离性和安全性。

        通过理解线程和进程的关系,可以更好地设计和管理程序,以实现高效的并发和资源利用。

二、如何在进程中创建线程

 1、创建线程的函数

        1、pthread_create():创建一个线程

#include <pthread.h>

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
功能:在进程中创建一个线程


参数:
参数1:
    pthread_t *thread:指针地址,指针对应的空间存储创建后的线程id
参数2:
    const pthread_attr_t *attr:线程的属性结构体指针,指定创建的线程的相关属性
            NULL:使用默认属性
参数3:
    
void *(*start_routine) (void *):函数指针,函数地址,存储 返回类型为 void * 参数为void * 函数的地址
        该函数指针是 线程的执行函数, 当创建线程后,线程就执行该函数地址对应的函数 
参数4:
    void *arg:当创建线程后,执行线程函数时,线程函数的参数

返回值:
成功,返回0
失败,返回非0

        2、pthread_exit():结束当前线程,并把结束状态利用参数地址传递出去

#include <pthread.h>

void pthread_exit(void *retval);
功能:退出当前线程,同时把参数作为结束状态返回给回收资源的线程

参数:
    void *retval:退出当前线程时,传递线程的退出状态值,是一个指针类型(地址)
    如果不想传递结束状态,则 填 NULL
    
当线程退出后,会有部分资源没有回收,需要使用pthread_join函数回收   

        3、pthread_join():阻塞等待指定线程的结束状态,回收指定线程资源


int pthread_join(pthread_t thread, void **retval);
功能:阻塞等待指定线程退出,接收该线程的退出状态,回收该线程的资源

参数:
参数1:
    pthread_t thread:指定等待哪个线程结束
参数2:
    void **retval:将目标线程的退出状态值(一级指针)拷贝到该二级指针(一级指针的地址)对应的内存空间中
        填 NULL,代表不接受线程的退出状态

返回值:
成功,返回0
失败,返回非0  

         2、举例使用:

//创建两个线程,一个线程拷贝文件一半,另一个线程拷贝后半段
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#include<pthread.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

//线程1拷贝文件的一半
void * thread_1(void *args)
{
	//打开文件
	int pid=open("1.txt",O_RDONLY);
	//拷贝文件的一半进入2.txt
	int pid_write1=open("2.txt",O_WRONLY|O_CREAT|O_TRUNC,0664);
	if(pid_write1<0)
	{
		printf("open failed\n");
		return NULL;
	}
	char buf[50];
	int num=*(int*)args;
	printf("进程1中的大小=%d\n",num);
	int size=num/2;
	while(1)
	{
		int ret=read(pid,buf,1);
		size--;
		if(size==0)
		{
			break;
		}
		write(pid_write1,buf,ret);
	}
	close(pid_write1);
	char * st=malloc(sizeof(char)*5);
	st="r";
	pthread_exit(st);


}
//线程2拷贝文件的另一半
void * thread_2(void *argz)
{
	//打开文件
	int pid=open("1.txt",O_RDONLY);
	//拷贝文件的一半进入3.txt
	int pid_write2=open("3.txt",O_WRONLY|O_CREAT|O_TRUNC,0664);
	if(pid_write2<0)
	{
		printf("open failed\n");
		return NULL;
	}
	char buf[50];
	int num=*(int *)argz;
	lseek(pid,num/2,SEEK_SET);
	while(1)
	{
		int ret=read(pid,buf,1);
		num--;
		if(num==0)
		{
			break;
		}
		write(pid_write2,buf,ret);
	}
	close(pid_write2);
    //定义结束状态地址,是一个地址,
	char *p=malloc(sizeof(char)*5);
	p="h";
	pthread_exit(p);

}
int main(int argc, const char *argv[])
{
	//打开文件
	int pid=open("1.txt",O_RDONLY);
	//计算文件大小
	int size=lseek(pid,0,SEEK_END);
	printf("%d\n",size);
	//计算完毕,将文件放到开头
	lseek(pid,0,SEEK_SET);
	close(pid);
	pthread_t tid1;
	pthread_t tid2;
	//创建两个线程
    //创建线程1
	int temp=pthread_create(&tid1,NULL,thread_1,&size);
	if(temp==0)
	{
		printf("创建成功\n");
	}
    //创建线程2
	pthread_create(&tid2,NULL,thread_2,&size);
	void *st;
	void *p;
    //接收指定线程的结束状态
	pthread_join(tid1,&st);
	pthread_join(tid2,&p);
	return 0;
}

 三、线程间的同步互斥机制

        1、什么是同步互斥机制 

        这是我第二次讲述同步互斥机制,这个机制主要是帮助我们在使用共享资源时,系统帮助我们完成共享资源调用不会出现错误的一个机制。,不清楚的可以看我上一篇文章

IPC机制(三)--共享内存和信号灯-CSDN博客

这里面讲述了为什么要使用同步互斥机制,在进程中有同步互斥,那么在线程中同样有这样的机制。

同步:线程间访问共享资源有顺序(唯一性,只有线程访问)

互斥:保证共享资源的完整性,只能同时有一个线程访问

 2、如何在线程中使用同步互斥机制

         在进程中使用同步互斥机制时,我们采用了信号灯集,信号量的方式来进行,信号的传输,这样就能实现同步和互斥的机制,我们一般使用下面的函数来进行线程间信号灯的使用。这里使用信号灯的函数和进程中使用信号灯的函数不一样,注意区别

1、sem_init():创建信号灯

#include <semaphore.h>

int sem_init(sem_t *sem, int pshared, unsigned int value);
功能: 创建并初始化信号量值

参数:
参数1:
    sem_t *sem:信号量变量的地址,把申请到的信号量存储到指定的内存地址空间
    
参数2:
    int pshared:共享标识
        0:用于线程的同步互斥
        非0:用于进程的同步互斥
        
参数3:
    unsigned int value:指定信号量的初始值        

返回值:
成功,返回0
失败,返回-1,设置错误码

        2、sem_wait():申请信号量,申请成功对信号量-1

#include <semaphore.h>

int sem_wait(sem_t *sem);
功能:申请信号量,对信号量的值-1

若信号量的值大于0,则申请信号量成功,此时把信号量的值-1,执行操作共享资源的代码
若信号量的值等于0,则申请信号量失败,当前线程进入休眠阻塞,等待信号量的值大于0

参数:
    sem_t *sem:要进行申请信号量的变量地址
    
返回值:
成功,返回0
失败,返回-1              

        3、sem_post():释放信号量,释放成功,对信号量+1

#include <semaphore.h>

int sem_post(sem_t *sem);
功能:释放信号量,对信号量的值+1

参数:
    sem_t *sem:要进行释放信号量的变量地址

返回值:
成功,返回0
失败,返回-1

 3、实际举例

//创建三个线程,每个线程循环打印自己的进程号,循环顺序ABC
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<semaphore.h>
sem_t A,B,C;

//创建线程
void *thread_funcA(void *args)
{
	while(1)
	{
		sem_wait(&C);
		printf("pidA=%d\n",getpid());
		sleep(1);
		sem_post(&A);
	}
	return NULL;
}
void *thread_funcB(void *args)
{
	while(1)
	{
		sem_wait(&A);
		printf("pidB=%d\n",getpid());
		sleep(1);
		sem_post(&B);
	}
	return NULL;
}
void *thread_funcC(void *args)
{
	while(1)
	{
		sem_wait(&B);
		printf("pidC=%d\n",getpid());
		sleep(1);
		sem_post(&C);
	}
	return NULL;
}
int main(int argc, const char *argv[])
{
	sem_init(&A,0,0);
	sem_init(&B,0,0);
	sem_init(&C,0,1);
	pthread_t tidA,tidB,tidC;
	//在进程中创建线程
	int retA = pthread_create(&tidA,NULL,thread_funcA,NULL);
	if(retA!=0)
	{
		printf("A no error\n");
	}
	int retB = pthread_create(&tidB,NULL,thread_funcB,NULL);
	if(retB!=0)
	{
		printf("B no error\n");
	}
	int retC = pthread_create(&tidC,NULL,thread_funcC,NULL);
	if(retC!=0)
	{
		printf("C no error\n");
	}
	//主线程回收子线程资源,通过地址来找到要回收的信息
	void *pthread_pA;
	void *pthread_pB;
	void *pthread_pC;
	pthread_join(tidA,pthread_pA);
	pthread_join(tidB,pthread_pB);
	pthread_join(tidC,pthread_pC);
	
	return 0;
}

总结 

         进程是资源分配的单位,线程是进程中的执行单元。一个进程可包含多个线程,线程共享进程的资源(如内存),但拥有自己的栈和执行路径。线程开销小、效率高,进程之间独立、资源隔离较好。进程间通信复杂。除此之外,还引入了进程间通信的同步互斥机制,这个机制只要是进程或者是线程在调用共享资源时,大部分情况下都会用到

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2108122.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

为什么要有RPC

​ 1. RPC&#xff08;Remote Procedure Call&#xff09; 定义&#xff1a; RPC&#xff08;Remote Procedure Call&#xff0c;远程过程调用&#xff09;是一种允许程序在不同的地址空间&#xff08;通常是在网络上的不同机器&#xff09;之间调用函数或方法的机制。它使得…

代码随想录算法训练营Day03 | 链表理论基础、203.移除链表元素 、707.设计链表、206.反转链表

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 链表理论基础203.移除链表元素思路与重点 707.设计链表思路与重点 206.反转链表思路与重点 链表理论基础 C/C的定义链表节点方式&#xff1a; // 单链表 struct L…

vue part 8

浏览器本地存储 application&#xff0c; local storage中 js方法肯定会用很多呀&#xff0c;只是不直接操作dom了但是对对象和数组进行操作还是原先的方法&#xff0c;jq的话想用引入就可以了。我是直接放弃jq了&#xff0c;在框架中用jq包不好 sessionStorage.HTML <!…

Git+word记笔记

程序员记笔记主要同步很重要&#xff0c;我这个方法只支持个人笔记&#xff0c;如果团队还是用企业微信开个企业会员比较方便。为什么用word&#xff0c;因为可以镶嵌代码和文档&#xff0c;不仅仅是文字&#xff0c;兼容性强 语雀&#xff0c;云笔记这些对于上传的word都是有…

AI大模型编写多线程并发框架(六十五):发布和应用

系列文章目录 文章目录 系列文章目录前言一、项目背景二、第十三轮对话-优化传参三、第十四轮对话-释放资源四、完善所有单元测试五、验证通过六、发布七、参考文章 前言 在这个充满技术创新的时代&#xff0c;AI大模型正成为开发者们的新宠。它们可以帮助我们完成从简单的问答…

IA——网络操作设备VRP简介

一&#xff0c;VRP简介 二&#xff0c;网络设备的管理 &#xff08;1&#xff09;console口&#xff1a; &#xff08;2&#xff09;talnet: &#xff08;3&#xff09;SSH: 安全的远程登陆 &#xff08;4&#xff09;通过WEB页面登录&#xff1a; 三&#xff0c;命令行常见…

TikTok养号一般养几天?账号起步方法

TikTok养号是一个关键的步骤&#xff0c;它可以帮助新账号快速积累粉丝和观众&#xff0c;增加视频的曝光和互动率&#xff0c;从而提升账号的影响力和可见性。但是养号也并不是简单的登录账号、互动点赞&#xff0c;而是从底层设备到分发频率都需要讲究方法&#xff0c;否则号…

linux下c语言中的单向列表,双向链表,内核双向列表,及适用场景

1. 单向链表&#xff08;Singly Linked List&#xff09; 1.1 定义与结构 单向链表是链式存储结构中最简单的一种。它的每个节点包含两个部分&#xff1a; - 数据域&#xff1a;存储数据元素 - 指针域&#xff1a;存储指向下一个节点的指针 在单向链表中&#xff0c;节点通过…

OpenHarmony实战开发:@Watch装饰器:状态变量更改通知

往期鸿蒙全套实战精彩文章必看内容&#xff1a; 鸿蒙开发核心知识点&#xff0c;看这篇文章就够了 最新版&#xff01;鸿蒙HarmonyOS Next应用开发实战学习路线 鸿蒙HarmonyOS NEXT开发技术最全学习路线指南 鸿蒙应用开发实战项目&#xff0c;看这一篇文章就够了&#xff08…

为什么要做智慧厕所,智慧公厕的建设意义有哪些?@卓振思众

智慧厕所是利用物联网、大数据、人工智能等技术&#xff0c;对传统厕所进行智能化升级改造后的新型厕所。它具备环境监测与调控、厕位引导、资源管理、安全管理、数据分析与管理平台等功能和特点。卓振思众是智慧厕所源头厂家&#xff0c;建设智慧厕所主要有以下几个重要原因&a…

【python因果推断库7】使用 pymc 模型的工具变量建模 (IV)2

目录 与普通最小二乘法 (OLS) 的比较 应用理论&#xff1a;政治制度与GDP 拟合模型&#xff1a;贝叶斯方法 多变量结果和相关性度量 结论 与普通最小二乘法 (OLS) 的比较 simple_ols_reg sk_lin_reg().fit(X.reshape(-1, 1), y)print("Intercept:", simple_ols_…

V90总线伺服报800F错误

1、博途PLC工艺对象位置轴轴控功能块 博途PLC工艺对象位置轴轴控功能块(完整SCL代码)-CSDN博客文章浏览阅读423次。S7-1200PLC脉冲轴位置轴位置控制功能块S7-1200PLC脉冲轴位置轴位置控制功能块优化(完整SCL源代码)_s71200 脉冲轴-CSDN博客文章浏览阅读341次。该博客详细介绍了…

自闭症儿童语言干预

自闭症儿童的语言发展往往面临独特挑战&#xff0c;这不仅影响了他们的日常交流能力&#xff0c;也制约了其社交与认知的全面发展。因此&#xff0c;实施科学有效的语言干预对于促进自闭症儿童的语言能力至关重要。 语言干预应基于个性化原则&#xff0c;充分考虑每个孩子的兴…

基于echarts车辆大数据综合分析平台

0.序言 基于ECharts的大数据综合分析平台技术框架与基本原理 技术框架 基于ECharts的大数据综合分析平台是一个集数据收集、处理、分析及可视化展示于一体的综合性系统。其技术框架主要可以分为以下几个层次&#xff1a; 数据源层&#xff1a; 数据收集&#xff1a;通过各种…

STM32F407ZET6

GPIO SPI 串行外设接口(Serial Peripheral Interface)的简称也叫做SPI,是一种高速的、全双工同步通信的一种接口,串行外设接口一般是需要4根线来进行通信(NSS、MISO、MOSI、SCK),但是如果打算实现单向通信(最少3根线,NSS、MOSI、SCK),就可以利用这种机制实现一对多或…

八、发票校验(1)

第一节 发票知识 1、发票介绍 发票是指一切单位和个人在购销商品、提供或接受服务以及从事其他经营活动中&#xff0c;所开具和收取的业务凭证&#xff0c;是会计核算的原始依据&#xff0c;也是审计机关、税务机关执法检查的重要依据。 发票必须具备的要素是根据议定条件由…

Xilinx系FPGA学习笔记(四)VIO、ISSP(Altera)及串口学习

系列文章目录 文章目录 系列文章目录VIO&#xff08;Vivado&#xff09;ISSP&#xff08;Altera&#xff09;串口学习FPGA串口发送FPGA串口接收 VIO&#xff08;Vivado&#xff09; VIO 的全称叫 Virtual Input/Output&#xff0c;建立一个虚拟的输入/输出信号&#xff0c;可以…

CRE6959AM70V055S 超低待机功耗反激式开关电源芯片

CRE6959AM70V055S 是一款高度集成的电流型 PWM控制 IC&#xff0c;为高性能、低待机功率、低成本、高效率的隔离型反激式开关电源控制器。在满载时&#xff0c;CRE6959AM70V055S工作在固定频率(65kHz)模式。在负载较低时&#xff0c;CRE6959AM70V055S采用节能模式&#xff0c;实…

前端XSS 攻击与SQL注入 处理

前端XSS 攻击与SQL注入 处理 文章目录 前端XSS 攻击与SQL注入 处理 一、XSS 攻击与SQL注入是什么二、XSS 攻击与SQL注入包含哪些方式1. XSS 攻击方式2. SQL 注入方式 三、如何避免XSS 攻击与SQL注入1. 避免XSS 攻击2. 避免SQL 注入 四、扩展与高级技巧1. XSS 防御策略2. SQL 注…

代码随想录算法训练营第32天 动态规划part01| 题目:理论基础 、 509. 斐波那契数 、70. 爬楼梯 、 746. 使用最小花费爬楼梯

代码随想录算法训练营第32天 动态规划part01| 题目&#xff1a;理论基础 、 509. 斐波那契数 、70. 爬楼梯 、 746. 使用最小花费爬楼梯 文章来源&#xff1a;代码随想录 理论 题目名称&#xff1a;509. 斐波那契数 斐波那契数&#xff0c;通常用 F(n) 表示&#xff0c;形成的…