进程与线程的区别
在了解线程与进程之间的区别前我们先来认识线程与进程,我们从概念入手,什么是线程、什么是进程,再进行进程与线程之间的不同点与相同点比较,优点与缺点进行比较理解。
进程的概念
进程(Process)就是程序的一次实例化,是分配资源的实体
进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动
是资源分配的基本单位
但是如果单单这样理解是完全不够的,我们必须要深入理解,怎么理解?
我们来结合进程中的PCB来理解,一个进程中有三大块,进程控制块(PCB)、数据段、代码段。我们主要分析PCB
首先在LInux下,用来描述PCB的是一个叫 task_struct 的结构体,来描述一个PCB,从而操作系统通过PCB来管理一个进程。
我们来看看 task_struct 主要的信息
- 标识符:用来描述本进程的唯一标识符,区别其他进程
- 状态:用来表示当前进程的状态,退出码,退出信号
- 优先级:相对于其他进程之间的优先级
- 程序计数器:用于记录程序的下一条指令的地址
- 内存指针:其中有代码和数据相关的指针,还有共享内存块指针
- 上下文:进程执行时寄存器中的数据 (用来程序切换是记录的上下文)
- I/O状态 :包括显示I/O请求,分配I/O设备和被进程使用的文件列表
- 记账信息:其中包含各种时间
- 其他
我么可以在Linux下用 top 命令查看当前进程
我们再看看进程的特征
动态性:进程的实质是程序在多道程序系统中的一次执行过程,进程是动态产生,动态消亡的。
并发性:任何进程都可以同其他进程一起并发执行
独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源的独立单位;
异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推进
结构特征:进程由程序、数据和进程控制块三部分组成。
线程的概念
什么是线程?
线程是进程中一个控制序列,每个进程都最少有一个线程,线程是调度的基本单位。在linux中线程也是一个轻量级的进程。
线程因为是轻量级进程也有三大块,线程控制块(在linux中是因为线程就是轻量级进程所以线程的控制块也叫PCB)、数据段、代码段。但是大部分都是共享的。
我们先看看在Linux下线程
在上面图中,PID为进程的ID、PPID为进程的父进程ID、LWP为线程的ID
简单介绍线程,我们来对比着学习
线程与进程的区别与联系
区别
资源分配:资源分配的基本单位,而线程是调度的基本单位
进程与进程之间是独立的,一个进程的异常终止不会影响其它进程,而线程与线程之间大部分是共享的,一个线程的异常终止会影响其它线程,会使进程终止。
线程与线程大部分共享,但是也有一部分数据私有,线程ID、上下文(切换时候寄存器中的值)、自己独享一个栈空间、错误码、信号屏蔽字、调度的优先级。
调度和切换:线程上下文切换比进程上下文切换要快得多。
一个进程中有多个线程时候,线程共享以下,在进程与进程之间的切换所花费消耗的大于线程与线程之间切换的花销
- 线程比进程占用的资源要小,一个线程的创建远小于进程的创建。
- 进程与进程之间是独立的,所以在并发过程中,用同步互斥少,相对安全,代码编写容易。线程之间大多数资源共享,所以往往要加上同步互斥锁。
- 线程创建出来的线程是平等的没有上下级,而进程创建出进程就为该进程的子进程
联系
- 进程与线程之间的关系:线程是存在进程的内部,一个进程中可以有多个线程,一个线程只能存在一个进程中。
- 一个线程的结束进程不一定会退出,但是进程的退出,线程将退出。
- 一个进程中的多个线程共享:1)一个进程中线程与线程之间同的虚拟地址空间、2)共享文件描述符、3)每种信号的处理方式、4)当前的工作目录、5)用户ID和组ID
进进程资源,防止造成僵尸进程。而线程也要进行等待,释放线程的资源(除过线程的分离)
线程ID和在进程地址空间的布局
线程ID在线程库NPTL中提供的pthread_create创建出来的是一个地址,我们可以通过
pid_t gettid(void) // 获取线程ID
通过
pid_t getpid(void) // 获取进程ID
进程的虚拟地址空间既线程在虚拟地址空间中的布局,我们来画一个图来解释
我们前面讲过,线程在进程中,那么线程中的自己私有的东西,是存放在mmap内存共享区中的。
那么线程ID为在每个维护自己线程的起始位置的地址。
线程的创建、等待与退出
我们是采用用POSIX线程库,来进行一些相关函数接口的解释,所以我们要在编译的时候添加上 -lpthread
线程的创建
先看函数
#include <pthread.h>
int phread_t create(pthread_t *thread, const pthread_attr* attr,\
void *(startt_routine)(void*), void * arg);
参数解释
thread:返回线程ID
attr:设置线程的属性,使用默认属性设置为NULL
start_routine:函数地址,是线程启动的入口函数
arg:传给线程入口函数的参数
返回值:成功返回0,失败返回错误码
我们调用函数就可以创建一个线程,如果计算机是多核计算机,那么创建出来的线程和原有的线程是并行执行。
线程的等待
线程等待,和前面将的进程等待的目的很相似,都是为了防止内存泄漏。线程是在进程中的虚拟地址空间的mmap地址共享区,当线程的创建会在共享区,创建出自己的栈和相关的数据,是自己私有的,所以当线程退出的时候,必须要释放,所以就需要进程的等待
等待函数
#include <phtread.h>
int pthread_join(pthread_t thread, void **value_ptr);
参数
thread:线程ID
value_ptr:指向的是线程返回值(输出型参数)
返回值:成功返回0,失败返回错误码。
如果对线程的终止状态不感兴趣,可以传NULL
线程的退出
我们类比进程的退出,进程有三种情况
1)程序执行完了结果正确
2)程序执行完了结果不正确
3)程序执行异常终止
那么线程相比进程来说,要简单一些
1)线程执行完了
2)线程异常终止
看线程的终止函数
#include <pthread.h>
// 线程的终止
void pthread_exit(void* value_ptr);
参数
value_ptr:线程结束的返回值,不可以用局部变量返回
#include <phtread.h>
// 一个线程将一个正在执行的线程取消掉
int pthread_cancel(pthread_t thread);
参数
thread:需要终止的线程ID
还有一个就是线程的函数的返回 return 。
下面我们来用代码具体演示一下:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
void *pthread_fun(void *arg)
{
(void)arg;
int i = 10;
while (i--)
{
sleep(1);
printf("i am thread\n");
// 如果要退出需要返回一个值,这个值一定不能在栈上
// pthread_exit(NULL);
}
pthread_t th = pthread_self();
printf("%lu\n", th);
// 如果要退出需要返回一个值,这个值一定不能在栈上
return NULL;
}
int main()
{
pthread_t thr;
// 线程创建成功返回0,失败返回错误码
// 传入thr, 线程属性,线程函数返回值为void*,线程函数参数为void*
int ret = pthread_create(&thr, NULL, pthread_fun, NULL);
if (ret != 0)
{
printf("error %d\n", ret);
exit(1);
}
int i = 5;
while (i--)
{
sleep(1);
printf("i am main thread\n");
}
// 线程的等待,第二参数为输出型参数,输入线程的返回值
// 有返回值,成功返回0,失败返回错误码
// 为阻塞式等待
/* void* value; */
// 这里可以接收pthread_exit(void* value)中的value,如果是空就必须在join中为NULL
pthread_join(thr, NULL); //输入型参数类型为void**
/* printf("join value %d\n", *(int*)value); // 这里要先强转为int*在解引用 */
return 0;
}
最后我们了解一下线程的分离。
线程分离为了提高程序运行的效率,有时候我们不关心线程退出的状态,所以就没必要等待线程的退出,那么前面所说的释放资源呢?当一个线程被设置为线程分离的时候,线程运行完成后,自动退出,并且释放资源。
int pthread_delete(pthread_t thread);
如有错误,还望多多指导!谢谢!