一、进程和线程

对操作系统进程和线程以及协程的了解,可以看看我前面的文章:

对进程、线程和协程的理解以及它们的区别:https://www.cnblogs.com/jiujuan/p/16193142.html。

这篇文章我用了多张图片,尽可能清楚表达对它们的理解和区别。

还有一篇 Linux 进程,源码分析,Linux进程: task_struct结构体成员:https://www.cnblogs.com/jiujuan/p/11715853.html

1.1 多进程和多线程

如果是单个进程,单个线程,那么就不涉及到并发。

并发都是涉及到多个进程、多个线程。

为了加快任务的处理,我们可以把一个任务分解成多个小任务进行处理。这就是多任务。

在计算机中,我们可以用多进程或多线程来处理多个任务,这样完成任务的效率就会提高,因为 CPU 运算速度非常快。

比如有一个任务,切菜炒菜后洗衣服:

把这个任务分为 3 个任务,1、切菜 2、炒菜 3、洗衣服,一个进程完成这个任务就按照 1,2,3 这样的次序完成。如下图:

如果是多进程,比如有 2 个进程,那么可以把上面的一个大任务分为 2 个小任务,第一个小任务 1 和 2,第二个小任务 3,由 2 个进程完成,如下图:

这就是并发执行。

为什么需要并发执行,要压榨 CPU 让 CPU 发挥最大运算效率,执行更多任务。

在计算机硬件组成中,CPU 的运算速度远远大于其它硬件设备的速度。

1.2 关系协调(进程间通信)

如果只有一个进程,就不需要沟通协调。

如果是多个进程,彼此之间有联系,那么就涉及到进程之间的沟通和协调。涉及沟通,就需要进程间进行"通话"了,这叫进程间通信。

进程间通信一般有 3 方面内容:

  1. 一个进程如何向另外一个进程传递信息。
  2. 多个进程操作共享数据时相互不会产生影响。
  3. 存在依赖关系时确定适当的顺序。

来自:《操作系统设计与实现》

不只多进程,多线程,还有多协程都会有上面 3 方面的内容。

二、协程

在计算机系统中,有一个层次关系,硬件位于最下面,操作系统控制硬件,应用程序运行在操作系统之上,更进一步理解,应用程序是运行在操作系统的用户空间中。

简图如下:

用户创建的多线程/多进程都是运行在用户空间,但调度它们运行是操作系统。

协程也是运行在用户空间,但调度协程运行的是各种语言自己的 runtime,比如 Go 语言的 goroutine 运行在 Go runtime 中。

也就是说在操作系统和调度协程运行之间增加了一个“中间层” - 语言自己的调度系统,比如 Go 语言的 GMP 调度模型,Go runtime 与操作系统线程挂钩进行调度处理,而不是协程直接被操作系统调度处理。

(在计算机中,没有什么是增加一层解决不了的)

Go 中 GMP 和操作系统线程的关系,可以看我这篇文章:https://www.cnblogs.com/jiujuan/p/16193142.html#2888921075 ,里面有一张图画出了它们之间的关系。

三、并发和并行

并发 concurrency 和并行 parallelism。

很多人容易把这 2 者搞混了,认为它们没有区别,其实是有区别。从 CPU 核心个数来理解,就比较容易懂了。

并发

如果 CPU 只有一个核心,那么它就只能并发执行任务,同一个时间只能执行一个任务。如下图:

并行

如果 CPU 有多个核心,那么它就可以并行的执行多个任务,可以在同一时间执行多个任务。

比如 CPU 有 2 个核心,它可以在同一时间同时执行 2 个任务:

而现代计算机往往有多个 CPU 核心数,Go 语言可以轻松利用多个核心执行程序。而多数编程语言需要写线程同步代码利用多个核,这样容易导致错误。Go 具体是用什么来执行?就是 goroutine。

四、进程/线程/协程间通信相关概念

通信的问题

多个进程线程需要协调,彼此之间就需要通信。那进程间通信一般会有什么问题?在上面第 1.2 小节已经有讲。

  • 第一个问题

    一个进程把信息传递给另外一个进程

  • 第二个问题

    两个或多个进程同时操作某一数据时,怎么保证多个进程间彼此不影响。

    比如在 12306 购票,你和其他用户抢最后一张票,系统用两个进程执行抢票操作,怎么保证 12306 只卖出最后一张票而不是两张票?

  • 第三个问题

    与顺序相关。最简单的例子就是打印机,A 进程生产数据,B 进程打印数据,B 打印前必须等待 A 产生一些数据。

竞争条件和临界区

竞争条件 - 两个或多个进程共享读写数据,而最后的结果取决于进程运行的精确时序,称为竞争条件(race condition)

怎么避免竞争条件?

凡涉及到共享内存、共享文件及共享任何资源都可能引起这种错误,要避免这种错误,关键是要找出某种途径来阻止多个进程同时读写共享的数据。换言之,我们需要互斥,即用某种手段确保当一个进程使用一个共享变量或文件时,其他进程不能做同样的操作。

-- 《现代操作系统》。

我们把共享内存进行访问的程序片段称为临界区临界区域。如果我们能使两个进程不可能同时处于临界区中,就能避免竞争条件。

忙等待互斥

书中提到了忙等待互斥的几个方法:Peterson 解法和 TSL 和 XCHG,这些解法本质上:当一个进程进入临界区,先检查是否允许进入,若不允许,则该进程将原地等待,直到允许位置。

缺点:浪费 CPU,还可能引起预想不到的结果。如果是等待时间非常短,则可以用。

睡眠与唤醒

进程无法进入临界区时将阻塞,而不是忙等待。

最简单的就是 sleep 和 wakeup。 sleep 是一个将引起系统进程阻塞的调用,即被挂起,直到另外一个进程将其唤醒。

典型应用就是生产者-消费者。

信号量

信号量(semaphore)是 E.W.Dijkstra 在 1965 年提出的一种方法,它使用一个整型变量来累计唤醒次数,供以后使用。

用信号量也可以解决生产者-消费者问题。

互斥量

互斥量是信号量最简化的一个版本,不需要计数。互斥量是一个处于两种状态之一的变量:加锁和解锁。

互斥量使用两个过程:

当一个线程需要访问临界区,它调用 mutex_lock。如果互斥量当前是解锁的(即临界区可用),此调用成功,调用线程可自由进入临界区。

另一方面,如果该互斥量已经加锁,调用线程阻塞,直到在临界区的线程完成并调用 mutex_unlock。

如果多个线程阻塞在互斥量上,将随机选择一个线程并允许它获得锁。

管程和条件变量

Brinch Hansen(1973)和Hoare(1974)提出了一种高级同步原语,称为管程(monitor)。

一个管程是有一个过程、变量及数据结构等组成的一个集合,他们组成一个特殊的包或者软件包。

管程是一种语言的概念。管程有一个很重要的特性,即任一时刻管程中只能有一个活跃进程,这一特性使管程能有效地完成互

斥。管程是编程语言的组成部分,编译器知道它们的特殊性。

进入管程时的互斥由编译器负责,但通常的做法是用一个互斥量或二元信号量。因为是由编译器而非程

序员来安排互斥,所以出错的可能性要小得多。在任一时刻,写管程的人无须关心编译器是如何实现互斥

的。他只需知道将所有的临界区转换成管程过程即可,决不会有两个进程同时执行临界区中的代码

当然解决生成者-消费者,缓存区满的问题,引入了条件变量(condition variables)以及相关2个操作:wait 和 signal。

在管程运行过程中发现生成者满时,它可以在条件变量上执行 wait 操作。该操作会导致调用进程自身阻塞,并且还将另一个以前等在管程之外的进程调入管程。

另外一个进程,比如消费者,还可以唤醒正在睡眠的伙伴进程。这可以通过对其伙伴正在等待的一个条件变量执行signal完成。

消息传递

进程间通信的方法使用两条原语 send 和 receive 。

Go 语言中通过 channel 在 goroutine 之间安全的传递消息。

五、操作系统进程间和Go语言通信方式有哪些

操作系统进程间通信方式

1、管道

2、消息队列

3、共享内存

4、信号量

5、信号

6、socket

1、管道

在 linux 系统的命令行下,运用 shell 命令来操作时,最常用的就是这种通信方式。

ps aux | grep nginx

管道 就是将前一个命令的输出内容作为下一个命令的输入内容,它的功能是一种单向命令传输。

管道又分为:1、 匿名管道 2、命名管道

匿名管道:上面的 shell 例子就是一种匿名管道,它没有名字。

命名管道:与上面相对应的就是有名字的管道,叫命名管道,它是 FIFO 结构,先进先出。

mkfifo pipename # pipename 就是管道的名字,mkfifo 是命令

2、消息队列

linux 内核也实现了消息队列,为进程间消息通信方式之一。

内核消息队列数据结构是一种链表,它里面的每一个数据都是一个单独的数据,叫消息体。

它与我们平时用的 kafka 消息队列作用差不多,是不是?kafka也是作为不同软件间消息通信的中间件,只不过 kafka 消息队列扩展出了很多功能,功能更多更强大。

3、共享内存

共享内存就是 2 个进程操作同一块内存。不需要拷贝来拷贝去的。

4、信号量

信号量其实是一种计数器,主要实现进程间的互斥和同步。

它并不缓存进程间的通信数据。

信号量是对资源计数,它有 2 个操作:

  • P 操作:减 1 操作。相减后信号量 < 0,资源被占用尽,进程需要阻塞等待;相减后信号量 >= 0,还有可用资源,进程可进入正常执行。
  • V 操作:加 1 操作。相加后信号量 <= 0,当前有阻塞中的进程,唤醒该进程运行;相加后信号量 > 0,当前没有阻塞的进程。

比如,多个进程操作共享内存时,在同一时刻不能有 2 个进程同时写数据到共享内存里,这样操作话,可能数据会发生错误,不是期望的数据。

5、信号

在 linux 中,为了响应各种事件操作,定义了很多信号,不同的信号代表不同的含义。

比如我们最常用的 SIGHUP 就是挂起。

当然还有很多信号,可以通过 kill -l 命令查询所有的信号。

信号信号量虽然只差了一个字,但是两者用途完全不一样,千万别搞混淆了。

6、Socket

socket 用于网络间的通信,不同计算机间之间进程间的通信。

最常用的就是在 TCP/IP 网络编程中。

而上面的 5 种通信方式都是在同一台计算机的进程间进行通信。

Go语言通信方式

Go 语言中采用 CSP 模型来进行通信。

CSP - Communicating Sequential Process,通信顺序进程。

这是一种用于描述两个独立的并发实体通过共享的通讯 Channel(管道)进行通信的并发模型。

在 Go 语言中,关于通信方式有一句非常有名的话:

不要通过共享内存来通信,而应该通过通信来共享内存

六、并发编程常见问题

  • 数据竞争

    当两个或更多操作必须以正确的顺序执行时,就会出现竞争状态。

  • 死锁

    所有并发进程都在彼此等待的状态,都不能运行。在这种情况下,如果没有外部干预,程序永远不会恢复。

    Go 运行时会检测一些死锁。

  • 活锁

    活锁就是并发的程序都可以运行,但好像彼此都在等待彼此,又都没运行。

  • 饥饿

    与死锁和活锁相似,它是指并发程序无法获得执行工作所需的资源。

七、Go协程:Goroutine

goroutine 是 Go 语言中进行并发编程一个很重要的概念。

goroutine 可以与其它 goroutine 并发(不一定是并行)执行函数,同时也会与主程序(main)并行执行,这个就是我们常说的主 goroutine。

goroutine 的一些优点:

  • 轻量级,比线程使用内存更少
  • 自主调度,效率高。Go 自己的 runtime 在用户空间调度 goroutine,不需要在内核空间和用户空间之间切换
  • 利用多核,提高程序执行速度
  • 避免阻塞,Go runtime 会监控协程所在的线程是否发生阻塞,如果阻塞,Go runtime 的调度器会把阻塞在线程上的协程调度到没有阻塞的线程上,继续运行。

怎么使用 goroutine?

Go 语言中运行并行编程关键字:go

func main() {
go sayHello() go func() {
fmt.Println("hello 2")
}() time.Sleep(3 * time.Second)
} func sayHello() {
fmt.Println("hello")
}

文章如果有不足或错误之处,欢迎大家批评指出,也欢迎大家评论

八、参考

Go语言并发编程(1):对多进程、多线程、协程和并发、并行的理解的更多相关文章

  1. python 之 并发编程(线程Event、协程)

    9.14 线程Event connect线程执行到event.wait()时开始等待,直到check线程执行event.set()后立即继续线程connect from threading impor ...

  2. Python 多进程 多线程 协程 I/O多路复用

    引言 在学习Python多进程.多线程之前,先脑补一下如下场景: 说有这么一道题:小红烧水需要10分钟,拖地需要5分钟,洗菜需要5分钟,如果一样一样去干,就是简单的加法,全部做完,需要20分钟:但是, ...

  3. python 多进程/多线程/协程 同步异步

    这篇主要是对概念的理解: 1.异步和多线程区别:二者不是一个同等关系,异步是最终目的,多线程只是我们实现异步的一种手段.异步是当一个调用请求发送给被调用者,而调用者不用等待其结果的返回而可以做其它的事 ...

  4. python采用 多进程/多线程/协程 写爬虫以及性能对比,牛逼的分分钟就将一个网站爬下来!

    首先我们来了解下python中的进程,线程以及协程! 从计算机硬件角度: 计算机的核心是CPU,承担了所有的计算任务.一个CPU,在一个时间切片里只能运行一个程序. 从操作系统的角度: 进程和线程,都 ...

  5. python并发编程-进程池线程池-协程-I/O模型-04

    目录 进程池线程池的使用***** 进程池/线程池的创建和提交回调 验证复用池子里的线程或进程 异步回调机制 通过闭包给回调函数添加额外参数(扩展) 协程*** 概念回顾(协程这里再理一下) 如何实现 ...

  6. web服务-2、四种方法实现并发服务器-多线程,多进程,协程,(单进程-单线程-非堵塞)

    知识点:1.使用多线程,多进程,协程完成web并发服务器 2.单进程-单线程-非堵塞也可以实现并发服务器 1.多进程和协程的代码在下面注释掉的部分,我把三种写在一起了 import socket im ...

  7. python教程:使用 async 和 await 协程进行并发编程

    python 一直在进行并发编程的优化, 比较熟知的是使用 thread 模块多线程和 multiprocessing 多进程,后来慢慢引入基于 yield 关键字的协程. 而近几个版本,python ...

  8. Python并发编程系列之多进程(multiprocessing)

    1 引言 本篇博文主要对Python中并发编程中的多进程相关内容展开详细介绍,Python进程主要在multiprocessing模块中,本博文以multiprocessing种Process类为中心 ...

  9. Python多线程、多进程和协程的实例讲解

    线程.进程和协程是什么 线程.进程和协程的详细概念解释和原理剖析不是本文的重点,本文重点讲述在Python中怎样实际使用这三种东西 参考: 进程.线程.协程之概念理解 进程(Process)是计算机中 ...

  10. 多线程、多进程、协程、IO多路复用请求百度

    最近学习了多线程.多进程.协程以及IO多路复用,那么对于爬取数据来说,这几个方式哪个最快呢,今天就来稍微测试一下 普通方式请求百度5次 import socket import time import ...

随机推荐

  1. [转帖]linux中批量多行缩进与添加空格

    用vim打开修改python脚本的时候,将代码整体向后移动4个空格操作如下: ESC之后,ctrl+v进入多行行首选中模式 使用上下键进行上下移动,选中多行行首 shift+i,进入插入模式 连续敲击 ...

  2. [转帖]rsar - Extract data from plain-text sar files

    sar -A -t -f /tmp/sa11 >/tmp/sar11 https://github.com/ryran/rsar When dealing with sysstat sar da ...

  3. [转帖]Guanaco, Llama, Vicuña, Alpaca该怎么区别

    https://zhuanlan.zhihu.com/p/106262896 在智利和秘鲁高原区经常会遇到的一种动物让人十分挠头,学术点称呼就是骆驼科其中一个族群--羊驼属和骆马属.头疼在于,分不清楚 ...

  4. [转帖]linux 查看CPU 内存的信息

    https://bbs.huaweicloud.com/blogs/302929   [摘要] ECS信息规格:2vCPUs | 4GiB | kc1.large.2镜像:openEuler 20.0 ...

  5. [转帖]jvm学习三-MAT内存分析工具的使用

    目录 1 模拟内存溢出程序 1.1 jvm配置 1.2 测试代码 2 MAT工具进行内存分析 2.1 大纲介绍 2.2 Histogram视图介绍 2.3 Leak Suspects视图介绍 2.4 ...

  6. [转帖]陈巍谈芯:NLP里比BERT更优秀的XLNet长什么样?

    https://zhuanlan.zhihu.com/p/447836322 ​ 目录 收起 一.XLNet的优势 1)独得AR与AE两大绝学 2)集成了Tansformer-XL 二.XLNet的结 ...

  7. 简单定位占用最高CPU的java进程信息

    公司里面一个应用不小心点击就会导致系统性能下降很明显. 性能组的同事定位到了, 我这里以学习的态度重现一下这个过程. 1. 问题再现 产品一个非常大数据量的帮助, 点击之后就会占用非常多的cpu 因为 ...

  8. 文心一言 VS 讯飞星火 VS chatgpt (186)-- 算法导论14.1 3题

    三.用go语言,写出OS-SELECT的非递归版本. 文心一言: 下面是使用Go语言编写的OS-SELECT的非递归版本: func OS_SELECT(T *Node, k int) *Node { ...

  9. SHA加密在实际应用中的优势与局限

    SHA加密算法简介 SHA(Secure Hash Algorithm)加密算法是一种单向加密算法,常用于加密数据的完整性校验和加密签名.它是由美国国家安全局(NSA)设计并广泛应用于各种安全场景.S ...

  10. 【小测试】rust中的无符号整数溢出

    作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢! cnblogs博客 zhihu Github 公众号:一本正经的瞎扯 1.在编译阶段就可以识别出来的溢出 fn main(){ ...