进程、线程、协程的对比、区别和联系,进程之间的通信方式、线程之间的通信方式、协程之间的通信方式

发布时间:2024年01月11日

前言

之前的一篇文章曾写过一些关于进程、线程、协程的内容——进程、线程、协程… … ——任务管理器的性能里都有什么?那么多的线程,进程、线程、句柄都是什么?

但对其之间的通信方式还是没有太过详细了解,因此特写此,归纳旧知识、学习新知识。

进程、线程、协程

此部分是八股文的复习,在我的上篇文章中总结过一些就直接复制来了,不完善地方还请大佬们补充。

概念

进程:进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体。

线程:线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位。一个进程可以有一个或多个线程,各个线程之间共享程序的内存空间。

协程:是一种基于线程之上,但又比线程更加轻量级的存在,这种由程序员自己写程序来管理的轻量级线程叫做『用户空间线程』,具有对内核来说不可见的特性。

线程与进程

1、线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位;
2、一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线;
3、进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间(包括代码段、数据集、堆等)及一些进程级的资源(如打开文件和信号),某进程内的线程在其它进程不可见;
4、调度和切换:线程上下文切换比进程上下文切换要快得多。

线程与协程

按我个人理解其实就是一句话:协程是用户态的,更迷你版本的线程。执行过程中分配给线程去执行 (具体过程就是gmp模型的内容了)

此处引用一张图 对其进行对比。

在这里插入图片描述

进程之间的通信方式

此部分学习参考自小林Coding 5.2 进程间有哪些通信方式?
引用的图也都来自于小林Coding 5.2 进程间有哪些通信方式?

每个进程的用户地址空间都是独立的,一般而言是不能互相访问的,但内核空间是每个进程都共享的,所以进程之间要通信必须通过内核。
在这里插入图片描述

1、管道。

概述

小林举例的指令没看过,所以找一个自己熟悉一些的——查看端口占用。

netstat -aon|findstr "8081"

在这其中,|就是管道。他的功能是将前一个指令的输出,作为后一个指令的输入——因此来说,管道是单向的。

基于Linux一切皆文件的思想,管道也是文件,实际上就是内核中的一串缓存。通过管道的一端写入数据,另一段读取数据,从而实现通信。父进程关闭f[0],只负责写;子进程关闭f[1],只负责读。双向通信需要两个管道!
在这里插入图片描述
对于匿名管道,只能是父子进程,之间通过fork来进行通信。
对于命名管道,就是创建了一个文件,所以实现不同进程的通信。

缺点

管道这种通信方式效率低,不适合进程间频繁地交换数据

消息队列

主要是解决了管道效率低的问题。

概述

消息队列的工作模式是“A把数据放到对应的消息队列,B需要用时候再去读取”。

其次,消息队列是保存在内核中的消息链表。发送的消息是用户自定义的数据类型,发送接受方要约定好,读完即删。

消息队列和管道的对比

1、消息队列发的是消息体(数据块),管道发的是字节流。
2、消息队列的生命周期随内核,直到释放或者操作系统关闭。管道的周期是随进程的创建而建立,随进程的结束而销毁。

缺点

1、消息队列不适合比较大数据的传输。
2、消息队列通信过程中,存在用户态与内核态之间的数据拷贝开销。

共享内存

主要是解决了 消息队列的读取和写入的过程,都会有发生用户态与内核态之间的消息拷贝过程 的问题。

概述

操作系统内存采取的是虚拟内存机制,每个进程都有自己独立的虚拟内存空间,不同进程的虚拟内存映射到不同的物理内存中。因此共享内存的机制,就是拿出一块虚拟地址空间来,映射到相同的物理内存中。
在这里插入图片描述

信号量

共享内存通信带来了新的问题——是如果多个进程同时修改同一个共享内存,很有可能就冲突了。因此使用信号量机制,保护共享的资源。

概述

信号量其实是一个整型的计数器,主要用于实现进程间的互斥与同步,而不是用于缓存进程间通信的数据。
一共有两个操作:P和V。
P代表-1,V代表+1,如果加减后的信号量。>0代表有空闲,<0代表没有空闲。==0时候,根据是加还是减进行判断。(此段原文拗口,不知道这样总结对不对)
信号量初始化为1时,则代表为互斥信号量。
信号量初始化为0时,则代表为同步信号量。(比如生产者消费者,先消费的话就会被阻塞,直到生产了才唤醒。)

信号

信号和信号量不是一回事!就好比Java和JavaScript的区别。
具体指的就是这些,比如常用的Ctrl+c终止进程,就是一个信号。
在这里插入图片描述

Socket

就是通常所知的那个socket网络通信。

实现 TCP 字节流通信: socket 类型是 AF_INET 和 SOCK_STREAM;
实现 UDP 数据报通信:socket 类型是 AF_INET 和 SOCK_DGRAM;
实现本地进程间通信: 「本地字节流 socket 」类型是 AF_LOCAL 和 SOCK_STREAM,「本地数据报 socket 」类型是 AF_LOCAL 和 SOCK_DGRAM。另外,AF_UNIX 和 AF_LOCAL 是等价的,所以 AF_UNIX 也属于本地 socket;

在这里插入图片描述

在这里插入图片描述

线程间通信机制

此部分看的很懵逼…貌似都是和语言相关一点?这里仅学习一下相关的,其余埋个坑日后再补了。
参考资料:

https://blog.csdn.net/J080624/article/details/87454764

互斥锁确保同一时间只能有一个线程访问共享资源。当锁被占用时试图对其加锁的线程都进入阻塞状态(释放CPU资源使其由运行状态进入等待状态)。当锁释放时哪个等待线程能获得该锁取决于内核的调度。

读写锁当以写模式加锁而处于写状态时任何试图加锁的线程(不论是读或写)都阻塞,当以读状态模式加锁而处于读状态时“读”线程不阻塞,“写”线程阻塞。读模式共享,写模式互斥。

条件变量可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。

自旋锁上锁受阻时线程不阻塞而是在循环中轮询查看能否获得该锁,没有线程的切换因而没有切换开销,不过对CPU的霸占会导致CPU资源的浪费。 所以自旋锁适用于并行结构(多个处理器)或者适用于锁被持有时间短而不希望在线程切换产生开销的情况。

协程间的通信机制

omg,作为一名gopher,八股的进程线程相关都要再加一个协程哈哈哈,学习量暴涨50%!(开玩笑)

Go语言是为并发而且生的语言,因此并发、协程也都是其中很重要的一个概念。自然要学习一下协程间的通信机制。

通过全局变量

全局变量是最简单理解、易于实现但是功能所限的一个方式。简单假设一个场景,主goroutine需要通知所有goroutine退出。

那么如何通过全局变量实现呢?——设置一个bool的全局变量,每个goroutine在运行时都自动检测bool的值,当他为false时,则结束。

如此看来,全局变量实现起来十分的简单。但其功能也十分受限——1、只能单向从主goroutine通知,不能接受回复,也不能实现子goroutine之间通信。
2、只能是一个写,多个去读。如果多个都想写,可以通过加锁来实现,但就太麻烦了,不值得。

channel通信

channel通信,就是不同协程之间通过channel传递发送信息。具体使用时,是通过WaitGroup实现的。
使用前wg.add(1),使用结束后wg.done(),同时通过Wait等待所有完成。
此外还有select机制,具体参考select机制

Context

Context是上下文,可以通过上下文传递协程的当前状态等信息。具体参考此文——上下文 Context

文章来源:https://blog.csdn.net/Ws_Te47/article/details/135515402
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。