Python之进程和线程

一、概念


    现在的 PC 都是多核的,使用多线程能充分利用 CPU 来提供程序的执行效率。

    进程==帮派,线程==各个堂口 函数==打手小弟

进程:可以理解为操作系统中正在执行的程序。也就说,每个应用程序都有一个自己的进程。实际是指一个程序在给定数据集合上的一次执行过程,是系统进行资源分配和运行调用的独立单位。没个进程启动时都会先创建一个主线程,随后根据需求让主线程创建子线程

线程:线程是一个基本的 CPU 执行单元。它必须依托于进程存活。一个线程是一个execution context(执行上下文),即一个 CPU 执行时所需要的一串指令。

区别:

        *    线程只能在某个进程中执行

        *    一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程(线程是计算机的最小单位);

        *    多线程共享同个地址空间、打开的文件以及其他资源。同一进程的所有线程共享该进程的所有资源。

        *    多进程共享物理内存、磁盘、打印机以及其他资源。资源分配给进程,进程与进程之间资源相互独立,互不影响(类似深拷贝)。

        *    由于GIL锁的缘故,python 中线程实际上是并发运行(即便有多个cpu,线程会在其中一个cpu来回切换,只占用一个cpu资源),而进程才是真正的并行运行(同时执行多个任务,占用多个cpu资源);

优缺点:

        1、多进程优点稳定性高:因为一个子进程崩溃了,不会影响主进程和其他子进程,多进程模式的缺点是在Windows下创建进程开销巨大。另外,操作系统能同时运行的进程数也是有限的,在内存和CPU的限制下,如果有几千个进程同时运行,操作系统连调度都会成问题(进程的创建比线程的创建更加占用计算机资源);

        2、多线程缺点就是任何一个线程挂掉都可能直接造成整个进程崩溃,因为所有线程共享进程的内存;

二、线程


1、分类:

    *    主线程

    *    子线程

    *    后台线程(守护线程)

    *    前台线程


2、创建:thread和threading 两种方式,一般使用threading 方式

    *    直接使用threading.Thread()

    *    继承threading.Thread来自定义线程类,重写run方法

3、线程同步与锁

    *    互斥锁:当多个线程对某一个共享数据进行操作时,有可能数据错乱,线程不安全,上锁,我用的时候你别用,

这个上锁超时和打印结果的逻辑后续补充    

    *    可重入锁(递归锁):RLock  同一线程对一个数据多次访问,这期间,不想让别的线程访问,要无耻的独占它,使用RLock锁上,我小弟们用完了,其他线程再上

4、线程合并:把子线程合并到主线程,子线程的活没干完,主线程不能结束退出

5、守护线程:主线程结束退出了,子线程不管干没干完也一起结束,

t1.setDaemon(True)

t1.start()

    在start前调用设置,默认False

6、定时器:规定函数在多少秒后执行


三、进程


1、创建;引用muiltprocessing库  

    *    直接使用Process:

基本和创建线程类似,proc.join()等待子进程结束后再继续往下运行,用于进程间的同步。

    *    继承Process 自定义进程类,重写run方法

2、进程池

    *    apply():同步执行(串行)

    *    apply_async():异步执行(并行)

    *    terminate():立刻关闭进程池

    *    join():主进程等待所有子进程执行完毕。必须在close或terminate()之后使用

    *    close():等待所有进程结束后,才关闭进程池


三、通信


1、Queue模块:是多进程安全的队列,可以实现多进程之间的数据传递。

(1)、 Queue(单向队列):同步的、线程安全的队列类,先进先出。实现了所有必需的锁定语义,用来在多个线程之间安全地交换信息。可以理解为它就是一个用来进程间数据通信的容器:公共方法如下:

        *    queue = Queue(maxsize=1):构造方法,maxsize参数为可插入数据的数量,超过设定值后,不让插了,默认值为0,意为无限大

        *    queue.qsize():返回队列大小,在多线程的情况下不可靠,因为在获取 qsize 时,其他线程可能又对队列进行操作了

        *    queue.empty():队列是否为空

        *    queue.full():队列是否满了

        *    queue.put(obj, block=True, timeout=None):存数据,obj=要存的数据,block=true,阻塞,当队列满了之后,put 就会阻塞,一直等待队列不再满时向里面添加数据,block=false,不阻塞,当队列满了之后,如果设置 put 不阻塞,或者等待时长到了之后会报错:queue.Full,timeout=超时时间,阻塞时等待时长,超过了就报queue.Full

        *    queue.get(block=True, timeout=None):取数据,block=true,阻塞,当队列空了之后,get 就会阻塞,一直等待队列中有数据后再获取数据,block=false,不阻塞,当队列空了之后,如果设置 get 不阻塞,或者等待时长到了之后会报错:_queue.Empty

        *    queue.get_nowait():等同于get(block=False),马上去,不等待,队列爱空不空,空就报错,我就想要当前的数据,防止多进程操作临时加了数据

        *    queue.put_nowait():同上,立存不等,牛B你报错

        *     queue.join():单线程的情况下,插入到主线程,队列里没数据了就往下执行,多线程的情况下,插入并阻塞主线程,配合task_done使用,队列空了之后才会放开主线程,执行之后的主线程操作和其他子线程操作

        *     queue.task_done():任务完成,多线程内的get方法后调用,get一次就要task_done一次,否则当队列数据为0的时候线程不会放开,依旧阻塞,我的理解是queue的存取数据类似列表,但是肯定不是,不然直接就用list了,那就说明他里面肯定还存了别的什么,结合task_done的使用,感觉他里面在多线程时是不是还存了一个task列表,只有这样才能说的通在get后必须用task-done。

            

2、Pipe

    Pipe的本质是进程之间的用管道数据传递,而不是数据共享,这和socket有点像。pipe() 返回两个连接对象分别表示管道的两端,每端都有send() 和recv()函数。

如果两个进程试图在同一时间的同一端进行读取和写入那么,这可能会损坏管道中的数据。


四、选择多线程还是多进程?


在这个问题上,首先要看下你的程序是属于哪种类型的。一般分为两种 CPU 密集型 和 I/O 密集型。 

    *    CPU 密集型:程序比较偏重于计算,需要经常使用 CPU 来运算。例如科学计算的程序,机器学习的程序等。

    *     I/O 密集型:顾名思义就是程序需要频繁进行输入输出操作。爬虫程序就是典型的 I/O 密集型程序。

 如果程序是属于 CPU 密集型,建议使用多进程。而多线程就更适合应用于 I/O 密集型程序。

GIL锁和并行、并发


并行:指两个或者多个事件在同一时刻发生,python中的进程属于并行能充分利用计算机资源效率最高同时执行多个任务,占用多个cpu资源;

并发:指两个或多个事件在同一时间间隔发生,python中的线程属于并发不管计算机有多少个CPU,不管你开了多少个线程,同一时间多个任务会在其中一个CPU来回切换,只占用一个CPU,效率并不高;















引用:1、https://www.jianshu.com/p/a69dec87e646

            2、http://c.biancheng.net/view/2603.html

你可能感兴趣的:(Python之进程和线程)