4.从linux到python:线程和进程

linux进程和线程:https://www.cnblogs.com/cxuanBlog/p/13277369.html

一.Linux进程和线程

1.进程和线程的区别

  • 进程是系统资源分配的最小单位,线程是系统调度的最小单位
  • 进程在初始化的时候,就会拥有一个独立的控制线程

https://blog.csdn.net/weixin_44602505/article/details/110893949
创建线程使用的底层函数和进程一样,都是clone。从内核里看进程和线程是一样的,都有各自不同的PCB,但是PCB中指向内存资源的三级页表是相同的。进程可以蜕变成线程。线程可看做寄存器和栈的集合。
实际上,无论是创建进程的fork,还是创建线程的pthread_create,底层实现都是调用同一个内核函数clone。如果复制对方的地址空间,那么就产出一个“进程”;如果共享对方的地址空间,就产生一个“线程”。
因此:Linux内核是不区分进程和线程的。只在用户层面上进行区分。所以,线程所有操作函数 pthread_* 是库函数,而非系统调用。

2.进程间通信方式

进程间通信通常被称为:IPC(Internel-Process communication)
(个人猜测:Internel内部是:相比于网络通信,IPC是内部的进程通信,走的是系统调用)
主要有6种:

image.png

这6种方式:都不会走网络通信(TCP/IP那一套,直接走内核的内存等进行通信)
网络socket(不同机器的不同进程)和命名socket的区别(同一台机器的不同进程)参考:
https://blog.csdn.net/weixin_45121946/article/details/105045387
(个人猜测:结合python实现猜测)

  • 命名Socket是最底层的实现方式
  • Pipe:基于memoryview+Socket实现
  • Queue:基于Pipe进一步封装实现等

3.进程管理系统调用

操作系统可以分为两种模式:

  • 内核态:操作系统内核使用的模式
  • 用户态:用户应用程序使用的模式

系统调用(函数):是引起内核态和用户态切换的一种方式。
与进程相关的主要的系统调用包括:
1.fork
fork用于创建一个与父进程相同的子进程,创建完进程后的子进程拥有和父进程一样的程序计数器、相同的CPU寄存器、相同的打开文件等
2.exec
exec 系统调用用于执行驻留在活动进程中的文件,调用 exec 后,新的可执行文件会替换先前的可执行文件并获得执行。也就是说,调用 exec 后,会将旧文件或程序替换为新文件或执行,然后执行文件或程序。新的执行程序被加载到相同的执行空间中,因此进程的 PID不会修改,因为我们没有创建新进程,只是替换旧进程。但是进程的数据、代码、堆栈都已经被修改。如果当前要被替换的进程包含多个线程,那么所有的线程将被终止,新的进程映像被加载执行。
备注:
进程映像(Process image)的概念
进程映象是执行程序时:所需要的可执行文件(进程启动后,程序加载到内存,内存的分配的映像)。通常包括下面这些东西

  • 代码段(codesegment/textsegment):又称文本段,用俩存放指令,运行代码的一块内存空间。此空间大小在代码运行前就已经确定。内存空间一般属于只读,某些架构的代码也允许可写。代码段中:也有可能包含一些只读的常数变量,例如字符串常量等
  • 数据段(datasegment):可读可写,存储初始化全局变量和初始化的static变量,数据段中的数据的生命周期是随程序持续性(随进程持续性)。随进程持续性指的是:进程创建就存在,进程死亡就消失。
  • bss段(bss segement):可读可写。存储未初始化的全局变量和未初始化的static变量。bss段中的数据一般默认为0
  • 栈(stack):可读可写。存储的是函数或代码中国呢的局部变量(非static变量),栈的生存期随代码块持续性,代码块运行就给你分配空间,代码块结束,就自动回收空间。
  • 堆(heap):可读可写。存储的是程序运行期间动态分配的malloc/relloc的空间,堆的生存期随进程持续性,从malloc/relloc到free一直存在。


    image.png

    3.waitpid
    等待子进程结束或终止
    4.exit
    在许多计算机操作系统上,计算机进程的终止是通过执行exit系统调用命令执行的。

二.python进程和线程

1.基本概念

1.进程间通信(IPC)

进程是孤立的,但是可以彼此通信。进程间通信(IPC)通常有两种方式:
1.基于消息传递
一条消息:就是一块原始字节的缓存。
基于消息的IPC通常有两种:

  • 管道
  • 队列

2.共享内存(mmap模块):内存映射区域
不太常见

2.共享数据的同步和访问

当多进程或者多线程需要共享数据时,就会出现数据同步和访问的问题。这也是并发编程常见的比较复杂的地方。

3.并发编程与python

python线程收到的限制比较多,主要原因是:python解释器内部使用了GIL(Global Interpreter Lock, 全局解释器锁)
GIL:使得在任意时刻只允许单个python线程执行,无论系统上存在多少个可用的CPU核。
GIL说明:
Python解释器别一个锁保护,只允许一次执行一个线程,即使存在多核。

  • 在计算密集型程序中:这严重限制了多线程的作用。事实上,在计算密集型程序中使用线程,经常比仅仅按照顺序执行同样的工作慢的多。通常用multiprocessing等模块替代。
  • 在I/O密集型程序中:可能比较适合。比如:网络服务器中使用线程。

2.multiprocessing

1.进程process类

用于创建和启动一个进程
使用subprocess中的Popen类进行实现。
底层还是调用os相关的接口,去创建进程等

1)创建子进程时:会对当前一份进程镜像的拷贝。所以:传递给子进程的函数的参数等,都会在子进程中有一份一模一样的拷贝。子进程中对参数等对象的修改,完全不会影响到主进程。
2)通过多进程通信(IPC)发送消息的方式:队列/管道中放入的项,在子进程中也是一个新的拷贝,修改其,不会影响到主进程中的该项。

2.进程间通信

1.Pipe类
单向/双向都支持
Pipe类使用:Connection类实现,Connection内部使用:memory+命名socket通信实现。
管道内部使用:pickle模块作为序列化
关于IPC通信命名socket(同一台机器不同进程)和网络通信socket的区别(不同机器之间的网络通信)详见:
https://blog.csdn.net/weixin_45121946/article/details/105045387
2.Queue类
单向:更高级封装
创建共享的进程队列。底层队列使用:管道和锁实现。另外,还需要运行支持线程以便将队列中的数据传输到底层管道中。
3.共享数据与同步(一般不建议使用)
其内部是基于mmap模块实现。

3.threading

由于GIL的存在,python的多线程可能更适用于IO密集型任务,而不太适合计算密集型任务。

1.线程Thread类

用于创建和启动一个现成
(个人猜测)
底层是通过:gevent(select、poll、epoll)等方式创建的线程。
线程使用有两种方式:

  • 创建Thread对象,传递可调用对象等
  • 继承Thread类,重写run方法。之后新的类也是线程类,创建对象,执行方法等
2.Timer类

Timer类继承Thread类,支持在给定时间后开始执行线程。

4.线程同步相关

并发编程(主要是多线程,当然多进程也要考虑,有其他方式解决)涉及到共享数据,就会有数据的同步的问题。为了解决同步,通常是加锁方式。

1.Lock对象(原语锁,是最底层的锁)

原语锁(或互斥锁):是一个同步原语
有两个状态:

  • 已锁定
  • 未锁定

方法:

  • Lock():创建新的Lock对象,初始状态为未锁定
  • lock.acquire([blocking]):获取锁,如果有必要,需要阻塞到锁释放为止。如果设置blocking=False,当无法获取锁时,将立即返回False,如果成功获取锁则返回为True。
  • lock.release():释放一个锁。当锁处于未锁定状态时,或者从原本调用acquire()方法的线程不同的线程调用此方法,将会出现错误。(即:只能由获取到锁的线程,进行锁的释放)

备注:
如果有多个线程等待锁,当锁被释放时,只有一个线程能获得到它。等待县城获得锁的顺序没有定义。

2.Rlock对象

可重入锁(reentrant lock):是一个同步原语
它允许拥有锁的线程执行嵌套的acquire()和release操作。在这种情况下,只有最外面的release()操作,才能将锁置为未锁定状态

3.信号量与有边界的信号量(用的比较少)

信号量是一个基于计数器的同步原语。可以通过设置value值,指定内部有多少信号量,可以用于线程的获取和释放。

4.Condition(条件变量,对原语锁进行封装)

1.condition用法介绍
条件变量是构建在锁上的同步原语,当需要线程关注特定的状态变化或事件的发生时将使用这个锁。
方法:

  • Condition([lock]):创建新的条件变量。lock是可选的Lock或Rlock实例。如果未提供lock参数,就会创建新的Rlock实例供条件变量使用。
  • cv.acquire(args):获取底层锁。此方法将调用底层锁上对应的acquire(args)方法
  • cv.release():释放底层锁。此方法将调用底层锁上对应的release()方法
  • cv.wait([timeout]):一直等待直到被唤醒,或者出现超时状态。
    此方法在调用线程已经获取锁之后调用。调用后:将释放底层锁,而且线程将进入睡眠状态,知道另一个线程在该条件变量上执行notify()或者notify_all()方法将其唤醒为止(通过内部锁实现)。在线程被唤醒后,线程将重新获取锁(重新获取底层锁,当然如果有多个线程在wait,同时被唤醒,这些线程都会去争取获得底层锁,但只有一个线程会获取到该底层锁,其他线程虽然被唤醒,但是会阻塞在获取外部锁的地方),方法也会返回。timeout是浮点数,单位为s。如果这单时间耗尽,线程将被唤醒,重新获取锁,而控制将被返回。
  • cv.notify([n]):唤醒一个或多个等待此条件变量的线程。此方法只会在带哦用线程已经获取锁之后调用。如果没有正在等待的线程,它就什么都不做。被唤醒的线程在他们重新获取底层锁之前不会从wait()返回
  • cv.notify_all():唤醒所有等待在此条件的线程。

2.condition的实现原理(源码解析)
http://timd.cn/python/threading/condition/
http://darr-en1.top/2020/07/20/1/
总结:
condition实现主要依靠两层锁:

  • condition初始化时创建一把锁(外部锁,或者叫底层锁),使用时需要先对外部锁上锁;
  • 每次调用wait时,会先生成一个lock锁(内部锁),将内部锁放到算双端队列waiters中,
    然后上锁,再将外部锁释放。并再次获取内部锁block(备注:第二次再调用acquire会阻塞当前线程),等待其他线程调用notify释放该内部锁


    image.png

    备注:
    其中finally:是一定会走的流程。


    image.png
5.event事件(最外层封装,对Condition做进一步封装,建议直接用Condition)

event用于线程之间通信。其底层是依赖Condition+flag实现。
一个线程发出"事件"信号,一个或多个其他线程等待线程信号。
flag含义:

  • True:表示某个线程发出了信号,将flag设置为True。
  • False:表示当前的flag是False

1.用法:

  • Event():创建新的Event实例,并将内部标志设为False。
  • e.is_set():只有当内部标志为True时才返回True
  • e.set():将内部标志设置为True。等待它变为True的所有线程都将被唤醒。(注意:虽然被唤醒,底层调用的是condition的wait方法,意味着:如果有多个线程等待,多个线程被唤醒后,会去竞争底层锁,只有一个线程能真正的获取到该锁,往下执行,其他线程依然阻塞在获取底层锁这里,等待下一次机会获取)
  • e.clear():将内部标志重制为False
  • e.wait([timeout]):线程阻塞在此event上,直到event玳标志为True。当然,如果进入时内部标志就为True,此方法将立即返回。否则,它将阻塞,直到另一个线程调用set()方法。

5.concurrent包

concurrent中目前只有一个模块:concurrent.futures
该模块通过对多线程或者多进程的进一步封装,提供异步执行可调用对象的更高层接口。(更高的封装意味着易用性更好,灵活性更差)
参考:
https://docs.python.org/zh-cn/3/library/concurrent.futures.html
贴上源码:

image.png

Future的实现原理简单分析(以ThreadPoolExecutor为例):
1.submit函数中:

  • 创建并返回future对象。
    ProcessPoolExector的submit


    image.png

    ThreadPoolExecutor的submit


    image.png

    2.启动线程并提交执行任务
    image.png

    3.执行workItem的run方法
    image.png

    4.执行函数fn,并将结果设置到future中


    image.png

5.Future的源码


image.png

image.png

image.png

6.总结

1.多线程和多进程

  • 多进程:进程资源是相互隔离的,所以一般不会有共享数据的问题。主要关注是:进程之间通信的问题。
  • 多线程:多线程是在同一个进程内,共享同一个进程资源。不会有通信问题,主要关注是:对共享数据的同步问题。

2.进程内通信(IPC)和网络通信

  • 进程内通信:主要是发送消息(对象序列化成字节的一块缓存),不走网络通信,其本质是:内存的偏移、拷贝等相关内存操作来完成。
  • 网络通信:需要走TCP/IP等网络,需要网卡支持。其也是将对象序列话成字节,然后通过网络发送、接受等。

7.其他

1.关于进程之间的参数都是拷贝,那么future在ProcessPoolExecutor中是如何实现异步的呢?

参考:Pool的apply


image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,258评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,335评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,225评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,126评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,140评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,098评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,018评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,857评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,298评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,518评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,400评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,993评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,638评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,661评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容

  • 1.进程和线程 1.1系统多任务机制 多任务操作机制的引入主要是在相同的硬件资源下怎么提高任务处理效率的!多任务的...
    _宁采臣阅读 978评论 0 6
  • 前言 拖了好久,不过还是得坚持。喜欢本文的话可以加下公众号【于你供读】。 目录 线程与进程 线程与进程是操作系统里...
    GitHubClub阅读 831评论 0 4
  • python之进程、线程与协程 有这么个例子说他们的区别,帮助理解很有用。 有一个老板想开一个工厂生产手机。 他需...
    道无虚阅读 3,177评论 0 3
  • 线程与进程 每一个应用程序都有一个自己的进程,操作系统会为这些进程分配一些执行资源,例如内存空间等。 在一个进程内...
    Day_cun阅读 168评论 0 2
  • Python多线程多进程 QUICK START 1.[endif]进程和线程 1.1系统多任务机制 多任务操作的...
    进化的程序猿阅读 567评论 0 0