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

目录

前言

一、进程和线程的关系

        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;
}

总结 

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

  • 24
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值