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

2022-10-11  本文已影响0人  celusing

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实现猜测)

3.进程管理系统调用

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

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

二.python进程和线程

1.基本概念

1.进程间通信(IPC)

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

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

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

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

3.并发编程与python

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

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)等方式创建的线程。
线程使用有两种方式:

2.Timer类

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

4.线程同步相关

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

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

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

方法:

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

2.Rlock对象

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

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

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

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

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

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

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

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

1.用法:

5.concurrent包

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

image.png

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

5.Future的源码


image.png
image.png
image.png

6.总结

1.多线程和多进程

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

7.其他

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

参考:Pool的apply


image.png
上一篇下一篇

猜你喜欢

热点阅读